Commit eeb58253b4 for wordpress.org

commit eeb58253b420cc050d4a5ca84903deb81e766dac
Author: isabel_brison <isabel_brison@git.wordpress.org>
Date:   Fri Sep 25 10:24:49 2026 +0000

    Editor: enable setting position within viewport states.

    Adds per-viewport output to the position block support.

    Props isabel_brison, ramonopoly.
    Fixes #66166.

    Built from https://develop.svn.wordpress.org/trunk@63929


    git-svn-id: http://core.svn.wordpress.org/trunk@63095 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/block-supports/position.php b/wp-includes/block-supports/position.php
index f8bc49c383..18968bb8be 100644
--- a/wp-includes/block-supports/position.php
+++ b/wp-includes/block-supports/position.php
@@ -29,10 +29,73 @@ function wp_register_position_support( $block_type ) {
 	}
 }

+/**
+ * Returns the CSS rules for a position style configuration.
+ *
+ * @since 7.2.0
+ *
+ * @param string $selector               CSS selector to scope the rules to.
+ * @param mixed  $position               Position style configuration.
+ * @param array  $allowed_position_types Position types the theme supports.
+ * @return array CSS rules, or an empty array when the configuration is not allowed.
+ */
+function wp_get_position_support_styles( $selector, $position, $allowed_position_types ) {
+	$styles = array();
+
+	if ( ! is_array( $position ) || ! is_string( $position['type'] ?? null ) ) {
+		return $styles;
+	}
+
+	$position_type = $position['type'];
+
+	if ( ! in_array( $position_type, $allowed_position_types, true ) ) {
+		return $styles;
+	}
+
+	$sides = array( 'top', 'right', 'bottom', 'left' );
+
+	foreach ( $sides as $side ) {
+		$side_value = $position[ $side ] ?? null;
+		if ( null !== $side_value ) {
+			/*
+			 * For fixed or sticky top positions,
+			 * ensure the value includes an offset for the logged in admin bar.
+			 */
+			if ( 'top' === $side ) {
+				// Ensure 0 values can be used in `calc()` calculations.
+				if ( '0' === $side_value || 0 === $side_value ) {
+					$side_value = '0px';
+				}
+
+				// Ensure current side value also factors in the height of the logged in admin bar.
+				$side_value = "calc($side_value + var(--wp-admin--admin-bar--position-offset, 0px))";
+			}
+
+			$styles[] = array(
+				'selector'     => $selector,
+				'declarations' => array(
+					$side => $side_value,
+				),
+			);
+		}
+	}
+
+	$styles[] = array(
+		'selector'     => $selector,
+		'declarations' => array(
+			'position' => $position_type,
+			'z-index'  => '10',
+		),
+	);
+
+	return $styles;
+}
+
 /**
  * Renders position styles to the block wrapper.
  *
  * @since 6.2.0
+ * @since 7.2.0 Added support for viewport states.
  * @access private
  *
  * @param string $block_content Rendered block content.
@@ -42,11 +105,31 @@ function wp_register_position_support( $block_type ) {
 function wp_render_position_support( $block_content, $block ) {
 	$block_type           = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
 	$has_position_support = block_has_support( $block_type, 'position', false );
+	$style_attribute      = $block['attrs']['style'] ?? null;

-	if (
-		! $has_position_support ||
-		empty( $block['attrs']['style']['position'] )
-	) {
+	if ( ! $has_position_support || ! is_array( $style_attribute ) ) {
+		return $block_content;
+	}
+
+	/*
+	 * Position styles can exist in either the default state or a viewport state.
+	 */
+	$has_position_style = ! empty( $style_attribute['position'] );
+	if ( ! $has_position_style ) {
+		foreach ( $style_attribute as $key => $style ) {
+			if (
+				is_string( $key ) &&
+				str_starts_with( $key, '@' ) &&
+				is_array( $style ) &&
+				! empty( $style['position'] )
+			) {
+				$has_position_style = true;
+				break;
+			}
+		}
+	}
+
+	if ( ! $has_position_style ) {
 		return $block_content;
 	}

@@ -63,58 +146,70 @@ function wp_render_position_support( $block_content, $block ) {
 		$allowed_position_types[] = 'fixed';
 	}

-	$style_attribute = $block['attrs']['style'] ?? null;
-	$class_name      = wp_unique_id( 'wp-container-' );
-	$selector        = ".$class_name";
-	$position_styles = array();
-	$position_type   = $style_attribute['position']['type'] ?? '';
-	$wrapper_classes = array();
-
-	if (
-		in_array( $position_type, $allowed_position_types, true )
-	) {
-		$wrapper_classes[] = $class_name;
-		$wrapper_classes[] = 'is-position-' . $position_type;
-		$sides             = array( 'top', 'right', 'bottom', 'left' );
-
-		foreach ( $sides as $side ) {
-			$side_value = $style_attribute['position'][ $side ] ?? null;
-			if ( null !== $side_value ) {
-				/*
-				 * For fixed or sticky top positions,
-				 * ensure the value includes an offset for the logged in admin bar.
-				 */
-				if (
-					'top' === $side &&
-					( 'fixed' === $position_type || 'sticky' === $position_type )
-				) {
-					// Ensure 0 values can be used in `calc()` calculations.
-					if ( '0' === $side_value || 0 === $side_value ) {
-						$side_value = '0px';
-					}
-
-					// Ensure current side value also factors in the height of the logged in admin bar.
-					$side_value = "calc($side_value + var(--wp-admin--admin-bar--position-offset, 0px))";
-				}
+	$viewport_settings        = $global_settings['viewport'] ?? null;
+	$responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
+	$class_name               = wp_unique_id( 'wp-container-' );
+	$selector                 = ".$class_name";
+	$position_styles          = array();
+	$wrapper_classes          = array();
+	$base_position            = $style_attribute['position'] ?? null;
+
+	// Default viewport (base) position styles.
+	$base_styles = wp_get_position_support_styles(
+		$selector,
+		$base_position,
+		$allowed_position_types
+	);
+
+	if ( ! empty( $base_styles ) ) {
+		$position_styles   = $base_styles;
+		$wrapper_classes[] = 'is-position-' . $base_position['type'];
+	}

-				$position_styles[] =
-					array(
-						'selector'     => $selector,
-						'declarations' => array(
-							$side => $side_value,
-						),
-					);
-			}
+	/*
+	 * Responsive viewport state styles. A viewport state inherits any values
+	 * it does not set from the default state, so a state that only overrides
+	 * e.g. the `top` offset keeps the base position type.
+	 */
+	foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
+		$viewport_position_style = $style_attribute[ $breakpoint ]['position'] ?? null;
+
+		if ( empty( $viewport_position_style ) || ! is_array( $viewport_position_style ) ) {
+			continue;
 		}

-		$position_styles[] =
-			array(
-				'selector'     => $selector,
-				'declarations' => array(
-					'position' => $position_type,
-					'z-index'  => '10',
+		$viewport_position = is_array( $base_position )
+			? array_replace( $base_position, $viewport_position_style )
+			: $viewport_position_style;
+
+		$viewport_styles = wp_get_position_support_styles(
+			$selector,
+			$viewport_position,
+			$allowed_position_types
+		);
+
+		if ( ! empty( $viewport_styles ) ) {
+			$wrapper_classes[] = 'is-position-' . $viewport_position['type'];
+		} elseif ( ! empty( $base_styles ) ) {
+			/*
+			 * The viewport state can explicitly clear the position type inherited from the
+			 * default state.
+			 */
+			$viewport_styles = array(
+				array(
+					'selector'     => $selector,
+					'declarations' => array( 'position' => 'static' ),
 				),
 			);
+		} else {
+			continue;
+		}
+
+		foreach ( $viewport_styles as $index => $rule ) {
+			$viewport_styles[ $index ]['rules_group'] = $media_query;
+		}
+
+		$position_styles = array_merge( $position_styles, $viewport_styles );
 	}

 	if ( ! empty( $position_styles ) ) {
@@ -132,7 +227,8 @@ function wp_render_position_support( $block_content, $block ) {
 		// Inject class name to block container markup.
 		$content = new WP_HTML_Tag_Processor( $block_content );
 		$content->next_tag();
-		foreach ( $wrapper_classes as $class ) {
+		$content->add_class( $class_name );
+		foreach ( array_unique( $wrapper_classes ) as $class ) {
 			$content->add_class( $class );
 		}
 		return (string) $content;
diff --git a/wp-includes/version.php b/wp-includes/version.php
index b296dc4789..5b6518dad0 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-63928';
+$wp_version = '7.2-alpha-63929';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.