PluginProbe
Gutenberg / trunk
Gutenberg vtrunk
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.2 → trunk 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 ) {