PluginProbe
Gutenberg / 24.1.0
Gutenberg v24.1.0
24.1.0 24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 All 404 releases
← All changes | lib/class-wp-theme-json-gutenberg.php +255 -119 23.6.0 → 24.1.0 View file →
@@ -124,8 +124,9 @@
124 124 * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`.
125 125 * @since 6.2.0 Added 'shadow' presets.
126 126 * @since 6.6.0 Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' and spacing size presets to use `spacing.defaultSpacingSizes`.
127 127 * @since 6.6.0 Added `aspectRatios`.
128 + * @since 7.2.0 Added 'textShadow' presets.
128 129 * @var array
129 130 */
130 131 const PRESETS_METADATA = array(
131 132 array(
@@ -186,8 +187,17 @@
186 187 'classes' => array( '.has-$slug-font-family' => 'font-family' ),
187 188 'properties' => array( 'font-family' ),
188 189 ),
189 190 array(
191 + 'path' => array( 'typography', 'textShadowPresets' ),
192 + 'prevent_override' => array( 'typography', 'defaultTextShadowPresets' ),
193 + 'use_default_names' => false,
194 + 'value_key' => 'textShadow',
195 + 'css_vars' => '--wp--preset--text-shadow--$slug',
196 + 'classes' => array( '.has-$slug-text-shadow' => 'text-shadow' ),
197 + 'properties' => array( 'text-shadow' ),
198 + ),
199 + array(
190 200 'path' => array( 'spacing', 'spacingSizes' ),
191 201 'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ),
192 202 'use_default_names' => true,
193 203 'value_key' => 'size',
@@ -392,8 +402,10 @@
392 402 * @since 7.0.0 Added type markers to the schema for boolean values.
393 403 * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
394 404 * `typography.textIndent` properties.
395 405 * @since 7.1.0 Added `viewport` property.
406 + * @since 7.2.0 Added `typography.textShadow`, `typography.textShadowPresets`,
407 + * and `typography.defaultTextShadowPresets`.
396 408 * @var array
397 409 */
398 410 const VALID_SETTINGS = array(
399 411 'appearanceTools' => null,
@@ -469,24 +481,27 @@
469 481 'presets' => null,
470 482 'defaultPresets' => null,
471 483 ),
472 484 'typography' => array(
473 - 'fluid' => null,
474 - 'customFontSize' => null,
475 - 'defaultFontSizes' => null,
476 - 'dropCap' => null,
477 - 'fontFamilies' => null,
478 - 'fontSizes' => null,
479 - 'fontStyle' => null,
480 - 'fontWeight' => null,
481 - 'letterSpacing' => null,
482 - 'lineHeight' => null,
483 - 'textAlign' => null,
484 - 'textColumns' => null,
485 - 'textDecoration' => null,
486 - 'textIndent' => null,
487 - 'textTransform' => null,
488 - 'writingMode' => null,
485 + 'fluid' => null,
486 + 'customFontSize' => null,
487 + 'defaultFontSizes' => null,
488 + 'dropCap' => null,
489 + 'fontFamilies' => null,
490 + 'fontSizes' => null,
491 + 'fontStyle' => null,
492 + 'fontWeight' => null,
493 + 'letterSpacing' => null,
494 + 'lineHeight' => null,
495 + 'textAlign' => null,
496 + 'textColumns' => null,
497 + 'textDecoration' => null,
498 + 'textIndent' => null,
499 + 'textTransform' => null,
500 + 'textShadow' => null,
501 + 'defaultTextShadowPresets' => null,
502 + 'textShadowPresets' => null,
503 + 'writingMode' => null,
489 504 ),
490 505 'viewport' => array(
491 506 'mobile' => null,
492 507 'tablet' => null,
@@ -651,9 +666,9 @@
651 666 *
652 667 * @since 7.1.0
653 668 *
654 669 * @param mixed $viewport_settings Viewport settings from theme.json.
655 - * @param array $options {
670 + * @param array $options {
656 671 * Optional. Options for generating media queries.
657 672 *
658 673 * @type bool $include_desktop Whether to include the desktop media query. Default false.
659 674 * }
@@ -842,8 +857,9 @@
842 857 'button' => '.wp-element-button, .wp-block-button__link',
843 858 // The block classes are necessary to target older content that won't use the new class names.
844 859 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption',
845 860 'cite' => 'cite',
861 + 'label' => 'label',
846 862 'select' => 'select',
847 863 'textInput' => 'textarea, input:where([type=email],[type=number],[type=password],[type=search],[type=text],[type=tel],[type=url])',
848 864 );
849 865
@@ -915,13 +931,15 @@
915 931
916 932 /**
917 933 * Processes pseudo-selectors for any node (block or variation).
918 934 *
919 - * @param array $node The node data (block or variation).
920 - * @param string $base_selector The base selector.
921 - * @param array $settings The theme settings.
922 - * @param string $block_name The block name.
923 - * @param array|null $block_metadata Metadata about the block to get styles for.
935 + * @since 7.0.0
936 + *
937 + * @param array $node The node data (block or variation).
938 + * @param string $base_selector The base selector.
939 + * @param array $settings The theme settings.
940 + * @param string $block_name The block name.
941 + * @param array|null $block_metadata Metadata about the block to get styles for.
924 942 * @param array|null $style_variation Style variation metadata.
925 943 * @return array Array of pseudo-selector declarations.
926 944 */
927 945 private function process_pseudo_selectors( $node, $base_selector, $settings, $block_name, $block_metadata = null, $style_variation = null ) {
@@ -1229,8 +1247,10 @@
1229 1247 *
1230 1248 * @since 5.8.0
1231 1249 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters.
1232 1250 * @since 6.6.0 Extended schema definition to allow enhanced block style variations.
1251 + * @since 7.1.1 Updated schema to allow responsive breakpoint states and pseudo-selectors
1252 + * at the top level of `styles` for block style variation partials.
1233 1253 *
1234 1254 * @param array $input Structure to sanitize.
1235 1255 * @param array $valid_block_names List of valid block names.
1236 1256 * @param array $valid_element_names List of valid element names.
@@ -1294,9 +1314,17 @@
1294 1314 }
1295 1315
1296 1316 $schema_styles_blocks = array();
1297 1317 $schema_settings_blocks = array();
1318 + $breakpoint_states = array_keys( $responsive_media_queries );
1298 1319
1320 + $common_block_settings = static::VALID_SETTINGS;
1321 + // `viewport` and `blockVisibility` are global-only settings and cannot be set per block for now.
1322 + unset(
1323 + $common_block_settings['viewport'],
1324 + $common_block_settings['blockVisibility']
1325 + );
1326 +
1299 1327 /*
1300 1328 * Generate a schema for blocks.
1301 1329 * - Block styles can contain `elements`, `variations`, and responsive breakpoint state definitions.
1302 1330 * - Variations definitions cannot be nested.
@@ -1304,23 +1332,29 @@
1304 1332 * - Variation inner `blocks` styles can contain `elements` and responsive breakpoint states.
1305 1333 *
1306 1334 * As each variation needs both a `blocks` schema and responsive `blocks` schemas
1307 1335 * for further nested inner `blocks`, the overall schema is generated in multiple passes.
1336 + *
1337 + * All blocks start with the same style schema. Build that common schema
1338 + * once, then add block-specific pseudo and custom states below.
1308 1339 */
1340 + $responsive_block_schema = $styles_non_top_level;
1341 + $responsive_block_schema['elements'] = $schema_styles_elements;
1342 +
1343 + $common_block_schema = $styles_non_top_level;
1344 + $common_block_schema['elements'] = $schema_styles_elements;
1345 +
1346 + foreach ( $breakpoint_states as $breakpoint_state ) {
1347 + $common_block_schema[ $breakpoint_state ] = $responsive_block_schema;
1348 + }
1349 +
1309 1350 foreach ( $valid_block_names as $block ) {
1310 - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
1311 - // `viewport` and `blockVisibility` are global-only settings and cannot be set per block for now.
1312 - unset( $schema_settings_blocks[ $block ]['viewport'] );
1313 - unset( $schema_settings_blocks[ $block ]['blockVisibility'] );
1314 - $schema_styles_blocks[ $block ] = $styles_non_top_level;
1315 - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
1351 + $schema_settings_blocks[ $block ] = $common_block_settings;
1352 + $schema_styles_blocks[ $block ] = $common_block_schema;
1316 1353
1317 - // Add responsive breakpoint states for all blocks.
1318 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
1319 - $schema_styles_blocks[ $block ][ $breakpoint_state ] = $styles_non_top_level;
1320 - $schema_styles_blocks[ $block ][ $breakpoint_state ]['elements'] = $schema_styles_elements;
1321 -
1322 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1354 + // Add responsive pseudo-selectors only to blocks that support them.
1355 + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1356 + foreach ( $breakpoint_states as $breakpoint_state ) {
1323 1357 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1324 1358 $schema_styles_blocks[ $block ][ $breakpoint_state ][ $pseudo_selector ] = $styles_non_top_level;
1325 1359 }
1326 1360 }
@@ -1375,9 +1409,8 @@
1375 1409 // Add responsive breakpoint states to block style variations.
1376 1410 foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
1377 1411 $variation_schema[ $breakpoint_state ] = $styles_non_top_level;
1378 1412 $variation_schema[ $breakpoint_state ]['elements'] = $schema_styles_elements;
1379 - $variation_schema[ $breakpoint_state ]['blocks'] = $schema_styles_blocks;
1380 1413
1381 1414 if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1382 1415 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1383 1416 $variation_schema[ $breakpoint_state ][ $pseudo_selector ] = $styles_non_top_level;
@@ -1405,8 +1438,49 @@
1405 1438 $schema['settings'] = static::VALID_SETTINGS;
1406 1439 $schema['settings']['blocks'] = $schema_settings_blocks;
1407 1440 $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA );
1408 1441
1442 + /*
1443 + * Add block style variation states to the top-level styles schema.
1444 + *
1445 + * Block style variations defined in a standalone JSON partial within a
1446 + * theme's `styles` directory declare their styles at the root of the
1447 + * `styles` object, so they are sanitized against the top-level schema.
1448 + * It needs to allow the same states that are allowed for variations
1449 + * declared inline in theme.json, otherwise those states are silently
1450 + * removed as unknown keys.
1451 + *
1452 + * The `blockTypes` property is only present on block style variation
1453 + * partials, so it both identifies the config as a variation and
1454 + * determines which pseudo-selectors are valid for it. Regular
1455 + * theme.json files are unaffected.
1456 + */
1457 + if ( ! empty( $input['blockTypes'] ) && is_array( $input['blockTypes'] ) ) {
1458 + $variation_pseudo_selectors = array();
1459 + foreach ( $input['blockTypes'] as $variation_block_type ) {
1460 + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $variation_block_type ] ) ) {
1461 + $variation_pseudo_selectors = array_merge(
1462 + $variation_pseudo_selectors,
1463 + static::VALID_BLOCK_PSEUDO_SELECTORS[ $variation_block_type ]
1464 + );
1465 + }
1466 + }
1467 + $variation_pseudo_selectors = array_unique( $variation_pseudo_selectors );
1468 +
1469 + foreach ( $breakpoint_states as $breakpoint_state ) {
1470 + $schema['styles'][ $breakpoint_state ] = $styles_non_top_level;
1471 + $schema['styles'][ $breakpoint_state ]['elements'] = $schema_styles_elements;
1472 +
1473 + foreach ( $variation_pseudo_selectors as $pseudo_selector ) {
1474 + $schema['styles'][ $breakpoint_state ][ $pseudo_selector ] = $styles_non_top_level;
1475 + }
1476 + }
1477 +
1478 + foreach ( $variation_pseudo_selectors as $pseudo_selector ) {
1479 + $schema['styles'][ $pseudo_selector ] = $styles_non_top_level;
1480 + }
1481 + }
1482 +
1409 1483 // Remove anything that's not present in the schema.
1410 1484 foreach ( array( 'styles', 'settings' ) as $subtree ) {
1411 1485 if ( ! isset( $input[ $subtree ] ) ) {
1412 1486 continue;
@@ -1469,9 +1543,9 @@
1469 1543 * separate selectors. If none of the start characters are present,
1470 1544 * there should be no way for a comma to mean anything other than a
1471 1545 * comma token. The exception are syntax errors, which are not handled here.
1472 1546 *
1473 - * @see https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1547 + * @link https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1474 1548 */
1475 1549 if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
1476 1550 return str_replace( ',', $to_append . ',', $selector ) . $to_append;
1477 1551 }
@@ -1518,9 +1592,9 @@
1518 1592 * separate selectors. If none of the start characters are present,
1519 1593 * there should be no way for a comma to mean anything other than a
1520 1594 * comma token. The exception are syntax errors, which are not handled here.
1521 1595 *
1522 - * @see https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1596 + * @link https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1523 1597 */
1524 1598 if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
1525 1599 return $to_prepend . str_replace( ',', ',' . $to_prepend, $selector );
1526 1600 }
@@ -1564,10 +1638,10 @@
1564 1638 *
1565 1639 * // Comments stay with the selector they follow.
1566 1640 * array( '.a /* a, the first *\/', '.b' ) === self::split_selector_list( '.a /* a, the first *\/,.b' );
1567 1641 *
1568 - * @see https://www.w3.org/TR/selectors/#parse-selector
1569 - * @see https://www.w3.org/TR/css-syntax-3/
1642 + * @link https://www.w3.org/TR/selectors/#parse-selector
1643 + * @link https://www.w3.org/TR/css-syntax-3/
1570 1644 *
1571 1645 * @param string $selector CSS selector list.
1572 1646 * @return string[] Selectors.
1573 1647 */
@@ -1670,10 +1744,10 @@
1670 1744 * > Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are
1671 1745 * > not included in this definition, as they are converted
1672 1746 * > to U+000A LINE FEED during preprocessing.
1673 1747 *
1674 - * @see https://www.w3.org/TR/css-syntax/#whitespace
1675 - * @see https://www.w3.org/TR/css-syntax/#newline
1748 + * @link https://www.w3.org/TR/css-syntax/#whitespace
1749 + * @link https://www.w3.org/TR/css-syntax/#newline
1676 1750 */
1677 1751 $selectors[] = trim( substr( $selector, $was_at, $next_at - $was_at ), " \t\n" );
1678 1752 $at = $next_at + 1;
1679 1753 $was_at = $at;
@@ -2109,9 +2183,9 @@
2109 2183 /**
2110 2184 * Returns the global styles custom CSS for a single block.
2111 2185 * This function is deprecated; please do not sync to core.
2112 2186 *
2113 - * @param array $css The block css node.
2187 + * @param array $css The block css node.
2114 2188 * @param string $selector The block selector.
2115 2189 *
2116 2190 * @return string The global styles custom CSS for the block.
2117 2191 */
@@ -2236,9 +2310,10 @@
2236 2310
2237 2311 // Gap styles will only be output if the theme has block gap support, or supports a fallback gap.
2238 2312 // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value.
2239 2313 if ( $has_block_gap_support || $has_fallback_gap_support ) {
2240 - $block_gap_value = null;
2314 + $block_gap_value = null;
2315 + $block_gap_row_value = null;
2241 2316 // Use a fallback gap value if block gap support is not available.
2242 2317 if ( ! $has_block_gap_support ) {
2243 2318 $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null;
2244 2319 if ( ! empty( $block_type ) ) {
@@ -2246,18 +2321,29 @@
2246 2321 }
2247 2322 } else {
2248 2323 $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) );
2249 2324 }
2325 + $block_gap_row_value = $block_gap_value;
2250 2326
2251 2327 // Support split row / column values and concatenate to a shorthand value.
2252 2328 if ( is_array( $block_gap_value ) ) {
2253 - if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) {
2254 - $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) );
2255 - $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) );
2256 - $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column;
2329 + $has_block_gap_row_value = isset( $block_gap_value['top'] );
2330 + $has_block_gap_column_value = isset( $block_gap_value['left'] );
2331 +
2332 + if ( $has_block_gap_row_value || $has_block_gap_column_value ) {
2333 + $block_gap_row_value = $has_block_gap_row_value
2334 + ? static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) )
2335 + : '0';
2336 + $block_gap_column_value = $has_block_gap_column_value
2337 + ? static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) )
2338 + : '0';
2339 + $block_gap_value = $block_gap_row_value === $block_gap_column_value
2340 + ? $block_gap_row_value
2341 + : $block_gap_row_value . ' ' . $block_gap_column_value;
2257 2342 } else {
2258 - // Skip outputting gap value if not all sides are provided.
2259 - $block_gap_value = null;
2343 + // Skip outputting a gap value if neither supported axis is provided.
2344 + $block_gap_value = null;
2345 + $block_gap_row_value = null;
2260 2346 }
2261 2347 }
2262 2348
2263 2349 // If the block should have custom gap, add the gap styles.
@@ -2267,10 +2353,13 @@
2267 2353 if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) {
2268 2354 continue;
2269 2355 }
2270 2356
2271 - $class_name = $layout_definition['className'] ?? false;
2272 - $spacing_rules = $layout_definition['spacingStyles'] ?? array();
2357 + $class_name = $layout_definition['className'] ?? false;
2358 + $spacing_rules = $layout_definition['spacingStyles'] ?? array();
2359 + $layout_gap_value = in_array( $layout_definition_key, array( 'default', 'constrained' ), true )
2360 + ? $block_gap_row_value
2361 + : $block_gap_value;
2273 2362
2274 2363 if (
2275 2364 ! empty( $class_name ) &&
2276 2365 ! empty( $spacing_rules )
@@ -2283,9 +2372,9 @@
2283 2372 ! empty( $spacing_rule['rules'] )
2284 2373 ) {
2285 2374 // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value.
2286 2375 foreach ( $spacing_rule['rules'] as $css_property => $css_value ) {
2287 - $current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value;
2376 + $current_css_value = is_string( $css_value ) ? $css_value : $layout_gap_value;
2288 2377 if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) {
2289 2378 $declarations[] = array(
2290 2379 'name' => $css_property,
2291 2380 'value' => $current_css_value,
@@ -2424,9 +2513,9 @@
2424 2513 * .has-value-gradient-background {
2425 2514 * background: value;
2426 2515 * }
2427 2516 *
2428 - * p.has-value-gradient-background {
2517 + * :where(p).has-value-gradient-background {
2429 2518 * background: value;
2430 2519 * }
2431 2520 *
2432 2521 * @since 5.9.0
@@ -2627,10 +2716,18 @@
2627 2716 foreach ( $slugs as $slug ) {
2628 2717 $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug );
2629 2718 $class_name = static::replace_slug_in_string( $class, $slug );
2630 2719
2631 - // $selector is often empty, so we can save ourselves the `append_to_selector()` call then.
2632 - $new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name );
2720 + /*
2721 + * $selector is often empty (root-level presets), in which case the
2722 + * bare class is used. For block-level presets the block selector is
2723 + * wrapped in `:where()` so the class keeps the same 0-1-0 specificity
2724 + * as a root-level preset. Without this, block-level palette rules
2725 + * (e.g. `p.has-x-color`) out-rank equally-important rules that also
2726 + * target the same property at 0-1-0, such as per-instance responsive
2727 + * state styles.
2728 + */
2729 + $new_selector = '' === $selector ? $class_name : ':where(' . $selector . ')' . $class_name;
2633 2730 $stylesheet .= static::to_ruleset(
2634 2731 $new_selector,
2635 2732 array(
2636 2733 array(
@@ -2980,15 +3077,15 @@
2980 3077 * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
2981 3078 * @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values.
2982 3079 * @since 6.7.0 Allow ref resolution of background properties.
2983 3080 *
2984 - * @param array $styles Styles to process.
2985 - * @param array $settings Theme settings.
2986 - * @param array $properties Properties metadata.
2987 - * @param array $theme_json Theme JSON array.
2988 - * @param string $selector The style block selector.
3081 + * @param array $styles Styles to process.
3082 + * @param array $settings Theme settings.
3083 + * @param array $properties Properties metadata.
3084 + * @param array $theme_json Theme JSON array.
3085 + * @param string $selector The style block selector.
2989 3086 * @param boolean $use_root_padding Whether to add custom properties at root level.
2990 - * @return array Returns the modified $declarations.
3087 + * @return array Returns the modified $declarations.
2991 3088 */
2992 3089 protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
2993 3090 if ( empty( $styles ) ) {
2994 3091 return array();
@@ -3112,10 +3209,10 @@
3112 3209 * @since 5.9.0 Added support for values of array type, which are returned as is.
3113 3210 * @since 6.1.0 Added the `$theme_json` parameter.
3114 3211 * @since 6.7.0 Added support for background image refs
3115 3212 *
3116 - * @param array $styles Styles subtree.
3117 - * @param array $path Which property to process.
3213 + * @param array $styles Styles subtree.
3214 + * @param array $path Which property to process.
3118 3215 * @param array $theme_json Theme JSON array.
3119 3216 * @return string|array Style property value.
3120 3217 */
3121 3218 protected static function get_property_value( $styles, $path, $theme_json = null ) {
@@ -3487,9 +3584,9 @@
3487 3584 * @since 6.1.0
3488 3585 *
3489 3586 * @param array $theme_json The theme.json converted to an array.
3490 3587 * @param array $selectors Optional list of selectors per block.
3491 - * @param array $options {
3588 + * @param array $options {
3492 3589 * Optional. An array of options for now used for internal purposes only (may change without notice).
3493 3590 *
3494 3591 * @type bool $include_block_style_variations Includes nodes for block style variations. Default false.
3495 3592 * @type bool $include_node_paths_only Return only block nodes node paths. Default false.
@@ -3694,24 +3791,47 @@
3694 3791 }
3695 3792 }
3696 3793 }
3697 3794 }
3698 - if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
3699 - foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
3795 + /*
3796 + * Elements can be styled outside any breakpoint, inside one, or both,
3797 + * so collect the names from all of those places before looping. An
3798 + * element styled only inside a breakpoint still needs a node.
3799 + */
3800 + $block_node = $theme_json['styles']['blocks'][ $name ] ?? array();
3801 + $element_names = array_keys( $block_node['elements'] ?? array() );
3802 + foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3803 + $element_names = array_merge(
3804 + $element_names,
3805 + array_keys( $block_node[ $breakpoint ]['elements'] ?? array() )
3806 + );
3807 + }
3808 + $element_names = array_unique( $element_names );
3809 +
3810 + if ( ! empty( $element_names ) ) {
3811 + foreach ( $element_names as $element ) {
3700 3812 $element_path = array( 'styles', 'blocks', $name, 'elements', $element );
3701 3813 if ( $include_node_paths_only ) {
3702 - $nodes[] = array(
3703 - 'path' => $element_path,
3704 - );
3814 + if ( isset( $block_node['elements'][ $element ] ) ) {
3815 + $nodes[] = array(
3816 + 'path' => $element_path,
3817 + );
3818 + }
3705 3819 continue;
3706 3820 }
3707 3821
3822 + if ( ! isset( $selectors[ $name ]['elements'][ $element ] ) ) {
3823 + continue;
3824 + }
3825 +
3708 3826 $element_selector = $selectors[ $name ]['elements'][ $element ];
3709 3827
3710 - $nodes[] = array(
3711 - 'path' => $element_path,
3712 - 'selector' => $element_selector,
3713 - );
3828 + if ( isset( $block_node['elements'][ $element ] ) ) {
3829 + $nodes[] = array(
3830 + 'path' => $element_path,
3831 + 'selector' => $element_selector,
3832 + );
3833 + }
3714 3834
3715 3835 // Responsive element nodes: one node per breakpoint that has
3716 3836 // styles for this element. Cascade: a{} → @media{a{}}
3717 3837 foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
@@ -3726,45 +3846,29 @@
3726 3846
3727 3847 // Handle any pseudo selectors for the element.
3728 3848 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
3729 3849 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
3730 - // Create element pseudo node if default or any responsive breakpoint has the pseudo.
3731 - $has_element_pseudo = isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] );
3732 - if ( ! $has_element_pseudo ) {
3733 - foreach ( array_keys( $responsive_media_queries ) as $bp ) {
3734 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $bp ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3735 - $has_element_pseudo = true;
3736 - break;
3737 - }
3738 - }
3850 + // Emit the default pseudo node only when the default state styles
3851 + // the pseudo. Otherwise get_styles_for_block() falls back to the
3852 + // element's base styles, outputting a rule the theme never defined.
3853 + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3854 + $nodes[] = array(
3855 + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ),
3856 + 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3857 + );
3739 3858 }
3740 3859
3741 - if ( $has_element_pseudo ) {
3742 - $element_pseudo_path = array( 'styles', 'blocks', $name, 'elements', $element );
3743 - if ( $include_node_paths_only ) {
3860 + // Responsive element pseudo nodes: one node per breakpoint
3861 + // that has this pseudo state for this element.
3862 + // Cascade: a:hover{} → @media{a:hover{}}
3863 + foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3864 + if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3744 3865 $nodes[] = array(
3745 - 'path' => $element_pseudo_path,
3866 + 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3867 + 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3868 + 'media_query' => $responsive_media_queries[ $breakpoint ],
3746 3869 );
3747 - continue;
3748 3870 }
3749 -
3750 - $nodes[] = array(
3751 - 'path' => $element_pseudo_path,
3752 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3753 - );
3754 -
3755 - // Responsive element pseudo nodes: one node per breakpoint
3756 - // that has this pseudo state for this element.
3757 - // Cascade: a:hover{} → @media{a:hover{}}
3758 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3759 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3760 - $nodes[] = array(
3761 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3762 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3763 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3764 - );
3765 - }
3766 - }
3767 3871 }
3768 3872 }
3769 3873 }
3770 3874 }
@@ -3847,10 +3951,20 @@
3847 3951 // Store variation metadata and node for layout styles generation.
3848 3952 // Only store if the variation has blockGap defined.
3849 3953 if ( isset( $style_variation_node['spacing']['blockGap'] ) ) {
3850 3954 // Append block selector to the variation selector for proper targeting.
3851 - $variation_metadata_with_selector = $style_variation;
3852 - $variation_metadata_with_selector['selector'] = $style_variation['selector'] . $block_metadata['css'];
3955 + $variation_metadata_with_selector = $style_variation;
3956 + $variation_metadata_with_selector['selector'] = $style_variation['selector'] . $block_metadata['css'];
3957 +
3958 + /*
3959 + * `get_layout_styles()` reads `name` as a block name, to check that the block
3960 + * supports layout at all. A variation node's `name` is the variation slug,
3961 + * which is never a registered block, so the check fails and every variation
3962 + * gap rule is discarded. Pass the block the variation belongs to, so the
3963 + * support check answers the question it is actually asking.
3964 + */
3965 + $variation_metadata_with_selector['name'] = $block_name;
3966 +
3853 3967 $style_variation_layout_metadata[ $style_variation['selector'] ] = array(
3854 3968 'metadata' => $variation_metadata_with_selector,
3855 3969 'node' => $style_variation_node,
3856 3970 );
@@ -3904,9 +4018,13 @@
3904 4018 // Process blockGap responsive layout styles for this variation.
3905 4019 if ( isset( $breakpoint_node['spacing']['blockGap'] ) ) {
3906 4020 $variation_layout_metadata = $style_variation;
3907 4021 $variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css'];
3908 - $variation_responsive_css .= $this->get_layout_styles(
4022 +
4023 + // The variation slug is not a block name here either. See above.
4024 + $variation_layout_metadata['name'] = $block_name;
4025 +
4026 + $variation_responsive_css .= $this->get_layout_styles(
3909 4027 $variation_layout_metadata,
3910 4028 array(
3911 4029 'node' => $breakpoint_node,
3912 4030 'media_query' => $breakpoint_media,
@@ -4569,9 +4687,9 @@
4569 4687 * Gets a `default`'s preset name by a provided slug.
4570 4688 *
4571 4689 * @since 5.9.0
4572 4690 *
4573 - * @param string $slug The slug we want to find a match from default presets.
4691 + * @param string $slug The slug we want to find a match from default presets.
4574 4692 * @param array $base_path The path to inspect. It's 'settings' by default.
4575 4693 * @return string|null
4576 4694 */
4577 4695 protected function get_name_from_defaults( $slug, $base_path ) {
@@ -4619,10 +4737,10 @@
4619 4737 * @since 5.9.0
4620 4738 * @since 6.6.0 Added support for block style variation element styles and $origin parameter.
4621 4739 *
4622 4740 * @param array $theme_json Structure to sanitize.
4623 - * @param string $origin Optional. What source of data this object represents.
4624 - * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
4741 + * @param string $origin Optional. What source of data this object represents.
4742 + * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
4625 4743 * @return array Sanitized structure.
4626 4744 */
4627 4745 public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) {
4628 4746 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
@@ -5666,8 +5784,9 @@
5666 5784 * This is used to convert the internal representation of variables to the CSS representation.
5667 5785 * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`.
5668 5786 *
5669 5787 * @since 6.3.0
5788 + * @since 7.2.0 Preset reference slugs are kebab-cased to match the generated custom properties.
5670 5789 * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert.
5671 5790 * @return string The converted variable.
5672 5791 */
5673 5792 private static function convert_custom_properties( $value ) {
@@ -5675,14 +5794,31 @@
5675 5794 $prefix_len = strlen( $prefix );
5676 5795 $token_in = '|';
5677 5796 $token_out = '--';
5678 5797 if ( str_starts_with( $value, $prefix ) ) {
5679 - $unwrapped_name = str_replace(
5680 - $token_in,
5681 - $token_out,
5682 - substr( $value, $prefix_len )
5683 - );
5684 - $value = "var(--wp--$unwrapped_name)";
5798 + $parts = explode( $token_in, substr( $value, $prefix_len ) );
5799 +
5800 + /*
5801 + * The slug of a preset reference is kebab-cased so the resulting
5802 + * custom property matches the one generated from the preset,
5803 + * whose slug is also kebab-cased (see `get_settings_values_by_slug()`).
5804 + * For slugs that are not already kebab-cased (e.g. `n27`), a verbatim
5805 + * conversion produces a reference to a custom property that does
5806 + * not exist (`--wp--preset--font-family--n27` instead of the
5807 + * generated `--wp--preset--font-family--n-27`).
5808 + *
5809 + * Duotone is the exception: its custom properties are generated by
5810 + * `WP_Duotone_Gutenberg` from the presets it registers in
5811 + * `get_all_global_styles_presets()`. Duotone references are
5812 + * kebab-cased all the same: the editor and the JS style engine
5813 + * kebab-case the references of every preset type, and
5814 + * `WP_Duotone_Gutenberg` looks up presets by kebab-cased filter ID.
5815 + */
5816 + if ( 3 === count( $parts ) && 'preset' === $parts[0] ) {
5817 + $parts[2] = _wp_to_kebab_case( $parts[2] );
5818 + }
5819 +
5820 + $value = 'var(--wp--' . implode( $token_out, $parts ) . ')';
5685 5821 }
5686 5822
5687 5823 return $value;
5688 5824 }
@@ -5691,9 +5827,9 @@
5691 5827 * Given a tree, converts the internal representation of variables to the CSS representation.
5692 5828 * It is recursive and modifies the input in-place.
5693 5829 *
5694 5830 * @since 6.3.0
5695 - * @param array $tree Input to process.
5831 + * @param array $tree Input to process.
5696 5832 * @return array The modified $tree.
5697 5833 */
5698 5834 private static function resolve_custom_css_format( $tree ) {
5699 5835 $prefix = 'var:';
@@ -5841,9 +5977,9 @@
5841 5977 * the variation class directly to the selector that will receive the
5842 5978 * declarations instead of deriving it by subtracting the root selector from
5843 5979 * the feature selector.
5844 5980 *
5845 - * @param array $style_variation Style variation metadata.
5981 + * @param array $style_variation Style variation metadata.
5846 5982 * @param string $feature_selector CSS selector for the feature.
5847 5983 * @return string Feature selector with block style variation selector added.
5848 5984 */
5849 5985 protected static function get_block_style_variation_feature_selector( $style_variation, $feature_selector ) {