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.1/12253.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/12253

* https://github.com/WordPress/gutenberg/pull/79210
10 changes: 5 additions & 5 deletions docs/how-to-guides/themes/global-settings-and-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -1060,12 +1060,12 @@ Pseudo selectors `:hover`, `:focus`, `:focus-visible`, `:visited`, `:active`, `:

#### Responsive styles

Block styles can be scoped to two named breakpoints: `mobile` and `tablet`. Any style property that is valid at the block or element level can be nested under one of these keys.
Block styles can be scoped to two named breakpoints: `@mobile` and `@tablet`. Any style property that is valid at the block or element level can be nested under one of these keys.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some drive-by smoke testing, it's working well and I like the syntax direction!

Especially @mobile / @tablet for responsive states... avoids style property clashes without changing the shape.

One thing that occurred to me for later in the cycle: as pseudo, responsive, and custom selector states grow, we'll probably want some small unifying doc for "style states" and their key prefixes.

They seem to be documented separately, so a convention exists but could be explicit.


| Key | Media query applied |
| --- | --- |
| `mobile` | `@media (width <= 480px)` |
| `tablet` | `@media (480px < width <= 782px)` |
| `@mobile` | `@media (width <= 480px)` |
| `@tablet` | `@media (480px < width <= 782px)` |

Responsive overrides can be placed directly on a block node:

Expand All @@ -1078,7 +1078,7 @@ Responsive overrides can be placed directly on a block node:
"color": {
"text": "black"
},
"mobile": {
"@mobile": {
"color": {
"text": "hotpink"
}
Expand Down Expand Up @@ -1110,7 +1110,7 @@ They can also be placed on element nodes within a block:
}
}
},
"mobile": {
"@mobile": {
"elements": {
"link": {
"color": { "text": "red" },
Expand Down
5 changes: 4 additions & 1 deletion lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,10 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {

$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$block_supports_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
$style_attr = $block['attrs']['style'] ?? array();
$style_attr = gutenberg_resolve_style_state_aliases(
$block['attrs']['style'] ?? array(),
$block['blockName']
);
// If there is any value in style -> layout, the block has a child layout.
$child_layout = $style_attr['layout'] ?? null;

Expand Down
7 changes: 5 additions & 2 deletions lib/block-supports/states.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Generates scoped CSS for per-instance state styles declared in block attributes,
* including pseudo-states (e.g., `style[':hover']`) and responsive states
* (e.g., `style['mobile']` and `style['mobile'][':hover']`).
* (e.g., `style['@mobile']` and `style['@mobile'][':hover']`).
*
* @package WordPress
*/
Expand Down Expand Up @@ -513,7 +513,10 @@ function gutenberg_render_block_states_support( $block_content, $block ) {
}

$supported_pseudo_states = WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
$style = $block['attrs']['style'] ?? array();
$style = gutenberg_resolve_style_state_aliases(
$block['attrs']['style'] ?? array(),
$block_name
);
$css_rules = array();

foreach ( $supported_pseudo_states as $pseudo_state ) {
Expand Down
24 changes: 15 additions & 9 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -629,19 +629,19 @@ class WP_Theme_JSON_Gutenberg {
* @var array
*/
const RESPONSIVE_BREAKPOINTS = array(
'mobile' => '@media (width <= 480px)',
'tablet' => '@media (480px < width <= 782px)',
'@mobile' => '@media (width <= 480px)',
'@tablet' => '@media (480px < width <= 782px)',
);

/**
* Custom states for blocks that map to CSS class selectors rather than
* CSS pseudo-selectors. Values use the '@' prefix (e.g. '@current') to
* distinguish them from real CSS pseudo-selectors.
* CSS pseudo-selectors. Values use the '-' prefix (e.g. '-current') to
* distinguish them from real CSS pseudo-selectors and breakpoint states.
*
* The CSS selector for each state is defined in the block's block.json
* under `selectors.states`, e.g.:
*
* "selectors": { "states": { "@current": ".some-css-selector" } }
* "selectors": { "states": { "-current": ".some-css-selector" } }
*
* This constant controls which states are valid in theme.json for a given
* block. Blocks listed here also inherit their VALID_BLOCK_PSEUDO_SELECTORS
Expand All @@ -651,7 +651,7 @@ class WP_Theme_JSON_Gutenberg {
* @var array
*/
const VALID_BLOCK_CUSTOM_STATES = array(
'core/navigation-link' => array( '@current' ),
'core/navigation-link' => array( '-current' ),
);

/**
Expand Down Expand Up @@ -888,7 +888,10 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gut
$origin = 'theme';
}

$this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
$this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
if ( isset( $this->theme_json['styles'] ) ) {
$this->theme_json['styles'] = gutenberg_resolve_style_state_aliases( $this->theme_json['styles'] );
}
$blocks_metadata = static::get_blocks_metadata();
$valid_block_names = array_keys( $blocks_metadata );
$valid_element_names = array_keys( static::ELEMENTS );
Expand Down Expand Up @@ -1158,7 +1161,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
}
}

// Add custom states for blocks that support them (e.g. '@current' for navigation).
// Add custom states for blocks that support them (e.g. '-current' for navigation).
if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $block ] ) ) {
foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $block ] as $custom_state ) {
$custom_state_schema = $styles_non_top_level;
Expand Down Expand Up @@ -3307,7 +3310,7 @@ private static function get_block_nodes( $theme_json, $selectors = array(), $opt
}
}

// Handle custom states (e.g. '@current' for navigation).
// Handle custom states (e.g. '-current' for navigation).
if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $name ] ) ) {
foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $name ] as $custom_state ) {
if (
Expand Down Expand Up @@ -4284,6 +4287,9 @@ public static function remove_insecure_properties( $theme_json, $origin = 'theme
$sanitized = array();

$theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
if ( isset( $theme_json['styles'] ) ) {
$theme_json['styles'] = gutenberg_resolve_style_state_aliases( $theme_json['styles'] );
}

$blocks_metadata = static::get_blocks_metadata();
$valid_block_names = array_keys( $blocks_metadata );
Expand Down
76 changes: 76 additions & 0 deletions lib/compat/plugin/style-state-aliases.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
/**
* Plugin-specific style state alias back compatibility.
*
* @package Gutenberg
*/

/**
* Resolves temporary aliases for persisted style state keys to their canonical keys.
*
* This keeps compatibility for Gutenberg plugin content created before
* responsive states used '@' and custom states used '-'. Remove after the
* deprecation window ends.
*
* DO NOT BACKPORT TO CORE.
*
* @param array $styles Persisted style data.
* @param string|null $block_name Current block name, when walking block styles.
* @return array Style data with canonical style state keys.
*/
function gutenberg_resolve_style_state_aliases( $styles, $block_name = null ) {
if ( ! is_array( $styles ) ) {
return $styles;
}

$responsive_breakpoint_aliases = array(
'@mobile' => 'mobile',
'@tablet' => 'tablet',
);

foreach ( $responsive_breakpoint_aliases as $state => $legacy_state ) {
if ( array_key_exists( $legacy_state, $styles ) && ! array_key_exists( $state, $styles ) ) {
$styles[ $state ] = $styles[ $legacy_state ];
}
unset( $styles[ $legacy_state ] );
}

$block_custom_state_aliases = array(
'core/navigation-link' => array(
'-current' => '@current',
),
);

if ( $block_name && isset( $block_custom_state_aliases[ $block_name ] ) ) {
foreach ( $block_custom_state_aliases[ $block_name ] as $state => $legacy_state ) {
if ( array_key_exists( $legacy_state, $styles ) && ! array_key_exists( $state, $styles ) ) {
$styles[ $state ] = $styles[ $legacy_state ];
}
unset( $styles[ $legacy_state ] );
}
}

foreach ( $styles as $key => $value ) {
if ( ! is_array( $value ) ) {
continue;
}

if ( 'blocks' === $key ) {
foreach ( $value as $child_block_name => $child_value ) {
$styles[ $key ][ $child_block_name ] = gutenberg_resolve_style_state_aliases( $child_value, $child_block_name );
}
continue;
}

if ( 'elements' === $key || 'variations' === $key ) {
foreach ( $value as $child_key => $child_value ) {
$styles[ $key ][ $child_key ] = gutenberg_resolve_style_state_aliases( $child_value, $block_name );
}
continue;
}

$styles[ $key ] = gutenberg_resolve_style_state_aliases( $value, $block_name );
}

return $styles;
}
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ function gutenberg_is_experiment_enabled( $name ) {
// Gutenberg plugin compat.
require __DIR__ . '/compat/plugin/edit-site-routes-backwards-compat.php';
require __DIR__ . '/compat/plugin/fonts.php';
require __DIR__ . '/compat/plugin/style-state-aliases.php';


// WordPress 7.0 compat.
Expand Down
13 changes: 10 additions & 3 deletions packages/block-editor/src/hooks/layout-child.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ import {
import useBlockVisibility from '../components/block-visibility/use-block-visibility';
import { deviceTypeKey } from '../store/private-keys';
import { BLOCK_VISIBILITY_VIEWPORTS } from '../components/block-visibility/constants';
import {
DEFAULT_BLOCK_STYLE_STATE,
getStyleForState,
} from './block-style-state';

// Used for generating the instance ID
const LAYOUT_CHILD_BLOCK_PROPS_REFERENCE = {};
// Keep in sync with WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS.
const RESPONSIVE_BREAKPOINTS = {
mobile: '@media (width <= 480px)',
tablet: '@media (480px < width <= 782px)',
'@mobile': '@media (width <= 480px)',
'@tablet': '@media (480px < width <= 782px)',
};

// These are the serialized `selfStretch` values. `max` used to be called
Expand Down Expand Up @@ -252,7 +256,10 @@ export function getResponsiveChildLayoutStyles( {

return Object.entries( RESPONSIVE_BREAKPOINTS )
.map( ( [ viewport, mediaQuery ] ) => {
const viewportLayout = style?.[ viewport ]?.layout;
const viewportLayout = getStyleForState( style, {
viewport,
pseudo: DEFAULT_BLOCK_STYLE_STATE.pseudo,
} )?.layout;
if ( ! viewportLayout || ! Object.keys( viewportLayout ).length ) {
return '';
}
Expand Down
9 changes: 6 additions & 3 deletions packages/block-editor/src/hooks/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ const layoutBlockSupportKey = 'layout';
// Keep in sync with WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS and
// packages/global-styles-engine/src/core/render.tsx.
const RESPONSIVE_BREAKPOINTS = {
mobile: '@media (width <= 480px)',
tablet: '@media (480px < width <= 782px)',
'@mobile': '@media (width <= 480px)',
'@tablet': '@media (480px < width <= 782px)',
};
const CHILD_LAYOUT_KEYS = [
'selfStretch',
Expand Down Expand Up @@ -252,7 +252,10 @@ export function getResponsiveLayoutStyles( {
} ) {
return Object.entries( RESPONSIVE_BREAKPOINTS )
.map( ( [ viewport, mediaQuery ] ) => {
const viewportStyle = attributes?.style?.[ viewport ];
const viewportStyle = getStyleForState( attributes?.style, {
viewport,
pseudo: DEFAULT_BLOCK_STYLE_STATE.pseudo,
} );
const viewportLayout = getLayoutContainerValues(
viewportStyle?.layout
);
Expand Down
4 changes: 2 additions & 2 deletions packages/block-editor/src/hooks/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const PSEUDO_STATE_LABELS = {
};

export const RESPONSIVE_STATE_LABELS = {
tablet: __( 'Tablet' ),
mobile: __( 'Mobile' ),
'@tablet': __( 'Tablet' ),
'@mobile': __( 'Mobile' ),
};

// Keep in sync with WP_Theme_JSON_Gutenberg::VALID_BLOCK_PSEUDO_SELECTORS
Expand Down
13 changes: 8 additions & 5 deletions packages/block-editor/src/hooks/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ const BORDER_SIDES = [ 'Top', 'Right', 'Bottom', 'Left' ];
// Keep in sync with WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS and
// packages/global-styles-engine/src/core/render.tsx.
const RESPONSIVE_BREAKPOINTS = {
mobile: '@media (width <= 480px)',
tablet: '@media (480px < width <= 782px)',
'@mobile': '@media (width <= 480px)',
'@tablet': '@media (480px < width <= 782px)',
};

const styleSupportKeys = [
Expand Down Expand Up @@ -419,7 +419,10 @@ export function getResponsiveStateCSSRules( style, name, baseSelector ) {

Object.entries( RESPONSIVE_BREAKPOINTS ).forEach(
( [ viewport, mediaQuery ] ) => {
const viewportStyles = style?.[ viewport ];
const viewportStyles = getStyleForState( style, {
viewport,
pseudo: DEFAULT_BLOCK_STYLE_STATE.pseudo,
} );
if ( ! viewportStyles ) {
return;
}
Expand Down Expand Up @@ -464,8 +467,8 @@ export function getResponsiveStateCSSRules( style, name, baseSelector ) {
* Returns the style value used to force-preview a selected state on canvas.
*
* Responsive pseudo states inherit from their default-viewport pseudo state.
* For example, selecting `mobile + :hover` should preview styles from
* `:hover`, with `mobile.:hover` values layered on top when present.
* For example, selecting `@mobile + :hover` should preview styles from
* `:hover`, with `@mobile.:hover` values layered on top when present.
*
* @param {Object} style Block style object.
* @param {Object} selectedState Selected block style state.
Expand Down
Loading
Loading