diff --git a/docs/how-to-guides/themes/theme-support.md b/docs/how-to-guides/themes/theme-support.md index a570493f390cc..64f8b0802303d 100644 --- a/docs/how-to-guides/themes/theme-support.md +++ b/docs/how-to-guides/themes/theme-support.md @@ -393,30 +393,18 @@ add_theme_support('custom-spacing'); ## Experimental — Link color control -Using the Gutenberg plugin (version 8.3 or later), link color control is available to the Paragraph, Heading, Group, Columns, and Media & Text blocks. This is off by default, and requires the theme to opt in by declaring support: +Using the Gutenberg plugin (version 8.3 or later), link color control is available to a number of blocks including Paragraph, Heading, Group, Columns, and Media & Text blocks. This is off by default, and requires the theme to opt in by declaring support: ```php add_theme_support('experimental-link-color'); ``` -If a theme opts in, it should [define default link colors](/docs/how-to-guides/themes/theme-json.md#color-properties) in `experimental-theme.json` (or in its theme styles if no `experimental-theme.json` is present). For example: +If a theme opts in, it can [define link colors](/docs/how-to-guides/themes/theme-json.md#color-properties) by using the `experimental-theme.json`. If the theme doesn't use the `experimental-theme.json` it can configure the color of links by settings the value of the `--wp--style--color--link` CSS Custom Property such as: ```css -{ - "global": { - "styles": { - "color": { - "link": "hotpink" - } - } - } +:root { + --wp--style--color--link: ; } ``` -If the theme styles the link color in its stylesheets (editor and front-end), it should ensure it maps to the `--wp--style--color--link` CSS variable: - -```css -a { - color: var( --wp--style--color--link ); -} -``` +The framework will take care of enqueing the necessary rules for this to work. Whether or not the theme supports `experimental-theme.json` the presets will also be enqueued as CSS Custom Properties, so themes can also use `--wp--style--color-link: var(--wp--preset--color--)`. See [the docs](/docs/how-to-guides/themes/theme-json.md#color-properties) for details. diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index cf02cf5fb877a..f3387aa1f2954 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -42,7 +42,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $used_layout = $block['attrs']['layout']; if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) { $tree = WP_Theme_JSON_Resolver::get_merged_data( array(), 'theme' ); - $default_layout = _wp_array_get( $tree->get_settings(), array( 'defaults', 'layout' ) ); + $default_layout = _wp_array_get( $tree->get_settings(), array( 'layout' ) ); if ( ! $default_layout ) { return $block_content; } diff --git a/lib/class-wp-theme-json-resolver.php b/lib/class-wp-theme-json-resolver.php index 86c3708b11103..94e39abe3e574 100644 --- a/lib/class-wp-theme-json-resolver.php +++ b/lib/class-wp-theme-json-resolver.php @@ -201,13 +201,13 @@ private static function translate( $theme_json, $domain = 'default' ) { /* * We need to process the paths that include '*' separately. * One example of such a path would be: - * [ 'settings', '*', 'color', 'palette' ] + * [ 'settings', 'blocks', '*', 'color', 'palette' ] */ $nodes_to_iterate = array_keys( $path, '*', true ); if ( ! empty( $nodes_to_iterate ) ) { /* * At the moment, we only need to support one '*' in the path, so take it directly. - * - base will be [ 'settings' ] + * - base will be [ 'settings', 'blocks' ] * - data will be [ 'color', 'palette' ] */ $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); @@ -219,7 +219,7 @@ private static function translate( $theme_json, $domain = 'default' ) { continue; } - // Whole path will be [ 'settings', 'core/paragraph', 'color', 'palette' ]. + // Whole path will be [ 'settings', 'blocks', 'core/paragraph', 'color', 'palette' ]. $whole_path = array_merge( $base_path, array( $node_name ), $data_path ); $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); gutenberg_experimental_set( $theme_json, $whole_path, $translated_array ); @@ -324,7 +324,7 @@ private static function get_user_data_from_custom_post_type( $should_create_cpt } elseif ( $should_create_cpt ) { $cpt_post_id = wp_insert_post( array( - 'post_content' => '{}', + 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 'post_status' => 'publish', 'post_title' => __( 'Custom Styles', 'default' ), 'post_type' => $post_type_filter, diff --git a/lib/class-wp-theme-json-schema-v0.php b/lib/class-wp-theme-json-schema-v0.php index 73e02cdf2dd73..5be707d5bc047 100644 --- a/lib/class-wp-theme-json-schema-v0.php +++ b/lib/class-wp-theme-json-schema-v0.php @@ -132,6 +132,28 @@ public static function parse( $old ) { // Copy everything. $new = $old; + // Overwrite the things that change. + if ( isset( $old['settings'] ) ) { + $new['settings'] = self::process_settings( $old['settings'] ); + } + if ( isset( $old['styles'] ) ) { + $new['styles'] = self::process_styles( $old['styles'] ); + } + + $new['version'] = WP_Theme_JSON::LATEST_SCHEMA; + + return $new; + } + + /** + * Processes the settings subtree. + * + * @param array $settings Array to process. + * + * @return array The settings in the new format. + */ + private static function process_settings( $settings ) { + $new = array(); $blocks_to_consolidate = array( 'core/heading/h1' => 'core/heading', 'core/heading/h2' => 'core/heading', @@ -153,30 +175,6 @@ public static function parse( $old ) { 'core/query-title/h6' => 'core/query-title', ); - // Overwrite the things that change. - if ( isset( $old['settings'] ) ) { - $new['settings'] = self::process_settings( $old['settings'], $blocks_to_consolidate ); - } - if ( isset( $old['styles'] ) ) { - $new['styles'] = self::process_styles( $old['styles'], $blocks_to_consolidate ); - } - - $new['version'] = 1; - - return $new; - } - - /** - * Processes the settings subtree. - * - * @param array $settings Array to process. - * @param array $blocks_to_consolidate A list of blocks that are transformed. - * - * @return array The settings in the new format. - */ - private static function process_settings( $settings, $blocks_to_consolidate ) { - $new = array(); - $paths_to_override = array( array( 'color', 'palette' ), array( 'color', 'gradients' ), @@ -260,12 +258,33 @@ private static function process_settings( $settings, $blocks_to_consolidate ) { * Processes the styles subtree. * * @param array $styles Array to process. - * @param array $blocks_to_consolidate A list of blocks that are transformed. * * @return array The styles in the new format. */ - private static function process_styles( $styles, $blocks_to_consolidate ) { - $new = array(); + private static function process_styles( $styles ) { + $new = array(); + $block_heading = array( + 'core/heading/h1' => 'h1', + 'core/heading/h2' => 'h2', + 'core/heading/h3' => 'h3', + 'core/heading/h4' => 'h4', + 'core/heading/h5' => 'h5', + 'core/heading/h6' => 'h6', + ); + $blocks_to_consolidate = array( + 'core/post-title/h1' => 'core/post-title', + 'core/post-title/h2' => 'core/post-title', + 'core/post-title/h3' => 'core/post-title', + 'core/post-title/h4' => 'core/post-title', + 'core/post-title/h5' => 'core/post-title', + 'core/post-title/h6' => 'core/post-title', + 'core/query-title/h1' => 'core/query-title', + 'core/query-title/h2' => 'core/query-title', + 'core/query-title/h3' => 'core/query-title', + 'core/query-title/h4' => 'core/query-title', + 'core/query-title/h5' => 'core/query-title', + 'core/query-title/h6' => 'core/query-title', + ); // Styles within root become top-level. if ( isset( $styles[ self::ROOT_BLOCK_NAME ] ) ) { @@ -287,9 +306,9 @@ private static function process_styles( $styles, $blocks_to_consolidate ) { * At this point, it only contains block's data. * However, we still need to consolidate a few things: * - * - link element => tranform from link color property + * - link element => transform from link color property * - heading elements => consolidate multiple blocks (core/heading/h1, core/heading/h2) - * into a single one with elements (core/heading with elements h1, h2, etc). + * into a single one (core/heading). */ $new['blocks'] = $styles; @@ -301,38 +320,40 @@ private static function process_styles( $styles, $blocks_to_consolidate ) { } } - // heading elements. - foreach ( $blocks_to_consolidate as $old_name => $new_name ) { + /* + * The heading block needs a special treatment: + * + * - if it has a link color => it needs to be moved to the blocks.core/heading + * - the rest is consolidated into the corresponding element + * + */ + foreach ( $block_heading as $old_name => $new_name ) { if ( ! isset( $new['blocks'][ $old_name ] ) ) { continue; } - if ( ! isset( $new['blocks'][ $new_name ] ) ) { - $new['blocks'][ $new_name ] = array(); - } + gutenberg_experimental_set( $new, array( 'elements', $new_name ), $new['blocks'][ $old_name ] ); - /* - * First, port the existing link color element to the block, - * overriding the previous value. - */ if ( isset( $new['blocks'][ $old_name ]['elements'] ) ) { - $new['blocks'][ $new_name ]['elements']['link'] = $new['blocks'][ $old_name ]['elements']['link']; - unset( $new['blocks'][ $old_name ]['elements'] ); + gutenberg_experimental_set( $new, array( 'blocks', 'core/heading', 'elements' ), $new['blocks'][ $old_name ]['elements'] ); } - /* - * Then, port any style categories left to the element - * resulting of exploding the previous block selector: - * - * - core/heading/h1 => h1 element - * - core/heading/h2 => h2 element - * - and so on - */ - if ( isset( $new['blocks'][ $old_name ] ) ) { - $element_name = explode( '/', $old_name )[2]; - $new['blocks'][ $new_name ]['elements'][ $element_name ] = $new['blocks'][ $old_name ]; - unset( $new['blocks'][ $old_name ] ); + unset( $new['blocks'][ $old_name ] ); + + } + + /* + * Port the styles from the old blocks to the new, + * overriding the previous values. + */ + foreach ( $blocks_to_consolidate as $old_name => $new_name ) { + if ( ! isset( $new['blocks'][ $old_name ] ) ) { + continue; } + + gutenberg_experimental_set( $new, array( 'blocks', $new_name ), $new['blocks'][ $old_name ] ); + unset( $new['blocks'][ $old_name ] ); + } return $new; diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php index 95447a0946358..5f01af35635c9 100644 --- a/lib/class-wp-theme-json.php +++ b/lib/class-wp-theme-json.php @@ -27,28 +27,6 @@ class WP_Theme_JSON { */ private static $blocks_metadata = null; - /** - * How to address all the blocks - * in the theme.json file. - */ - const ALL_BLOCKS_NAME = 'defaults'; - - /** - * The CSS selector for the * block, - * only using to generate presets. - * - * @var string - */ - const ALL_BLOCKS_SELECTOR = 'body'; - - /** - * How to address the root block - * in the theme.json file. - * - * @var string - */ - const ROOT_BLOCK_NAME = 'root'; - /** * The CSS selector for the root block. * @@ -61,6 +39,7 @@ class WP_Theme_JSON { 'templateParts', 'styles', 'settings', + 'version', ); const VALID_STYLES = array( @@ -271,14 +250,33 @@ class WP_Theme_JSON { ), ); + const ELEMENTS = array( + 'link' => 'a:not(.wp-block-button_link)', + 'h1' => 'h1', + 'h2' => 'h2', + 'h3' => 'h3', + 'h4' => 'h4', + 'h5' => 'h5', + 'h6' => 'h6', + ); + + const LATEST_SCHEMA = 1; + /** * Constructor. * * @param array $theme_json A structure that follows the theme.json schema. */ public function __construct( $theme_json = array() ) { - $valid_block_names = array_keys( self::get_blocks_metadata() ); - $this->theme_json = self::sanitize( $theme_json, $valid_block_names ); + // The old format is not meant to be ported to core. + // We can remove it at that point. + if ( ! isset( $theme_json['version'] ) || 0 === $theme_json['version'] ) { + $theme_json = WP_Theme_JSON_Schema_V0::parse( $theme_json ); + } + + $valid_block_names = array_keys( self::get_blocks_metadata() ); + $valid_element_names = array_keys( self::ELEMENTS ); + $this->theme_json = self::sanitize( $theme_json, $valid_block_names, $valid_element_names ); } /** @@ -286,10 +284,11 @@ public function __construct( $theme_json = array() ) { * * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. + * @param array $valid_element_names List of valid element names. * * @return array The sanitized output. */ - private static function sanitize( $input, $valid_block_names ) { + private static function sanitize( $input, $valid_block_names, $valid_element_names ) { $output = array(); if ( ! is_array( $input ) ) { @@ -298,12 +297,26 @@ private static function sanitize( $input, $valid_block_names ) { $output = array_intersect_key( $input, array_flip( self::VALID_TOP_LEVEL_KEYS ) ); - $schema = array(); - foreach ( $valid_block_names as $block_name ) { - $schema['styles'][ $block_name ] = self::VALID_STYLES; - $schema['settings'][ $block_name ] = self::VALID_SETTINGS; + // Build the schema based on valid block & element names. + $schema = array(); + $schema_styles_elements = array(); + foreach ( $valid_element_names as $element ) { + $schema_styles_elements[ $element ] = self::VALID_STYLES; + } + $schema_styles_blocks = array(); + $schema_settings_blocks = array(); + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = self::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = self::VALID_STYLES; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } + $schema['styles'] = self::VALID_STYLES; + $schema['styles']['blocks'] = $schema_styles_blocks; + $schema['styles']['elements'] = $schema_styles_elements; + $schema['settings'] = self::VALID_SETTINGS; + $schema['settings']['blocks'] = $schema_settings_blocks; + // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { continue; @@ -355,12 +368,15 @@ private static function to_property( $css_name ) { * Example: * * { - * 'root': { - * 'selector': 'body' + * 'core/paragraph': { + * 'selector': 'p' * }, - * 'core/heading/h1': { + * 'core/heading': { * 'selector': 'h1' * } + * 'core/group': { + * 'selector': '.wp-block-group' + * } * } * * @return array Block metadata. @@ -370,61 +386,30 @@ private static function get_blocks_metadata() { return self::$blocks_metadata; } - self::$blocks_metadata = array( - self::ROOT_BLOCK_NAME => array( - 'selector' => self::ROOT_BLOCK_SELECTOR, - ), - self::ALL_BLOCKS_NAME => array( - 'selector' => self::ALL_BLOCKS_SELECTOR, - ), - ); + self::$blocks_metadata = array(); $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); foreach ( $blocks as $block_name => $block_type ) { - /* - * Assign the selector for the block. - * - * Some blocks can declare multiple selectors: - * - * - core/heading represents the H1-H6 HTML elements - * - core/list represents the UL and OL HTML elements - * - core/group is meant to represent DIV and other HTML elements - * - * Some other blocks don't provide a selector, - * so we generate a class for them based on their name: - * - * - 'core/group' => '.wp-block-group' - * - 'my-custom-library/block-name' => '.wp-block-my-custom-library-block-name' - * - * Note that, for core blocks, we don't add the `core/` prefix to its class name. - * This is for historical reasons, as they come with a class without that infix. - * - */ if ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) { - self::$blocks_metadata[ $block_name ] = array( - 'selector' => $block_type->supports['__experimentalSelector'], - ); - } elseif ( - isset( $block_type->supports['__experimentalSelector'] ) && - is_array( $block_type->supports['__experimentalSelector'] ) - ) { - foreach ( $block_type->supports['__experimentalSelector'] as $key => $selector_metadata ) { - if ( ! isset( $selector_metadata['selector'] ) ) { - continue; - } + self::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector']; + } else { + self::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); + } - self::$blocks_metadata[ $key ] = array( - 'selector' => $selector_metadata['selector'], - ); + // Assign defaults, then overwrite those that the block sets by itself. + // If the block selector is compounded, will append the element to each + // individual block selector. + $block_selectors = explode( ',', self::$blocks_metadata[ $block_name ]['selector'] ); + foreach ( self::ELEMENTS as $el_name => $el_selector ) { + $element_selector = array(); + foreach ( $block_selectors as $selector ) { + $element_selector[] = $selector . ' ' . $el_selector; } - } else { - self::$blocks_metadata[ $block_name ] = array( - 'selector' => '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ), - ); + self::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); } } @@ -583,7 +568,7 @@ private static function has_properties( $metadata ) { * ``` * * @param array $declarations Holds the existing declarations. - * @param array $styles Styles to process. + * @param array $styles Styles to process. * * @return array Returns the modified $declarations. */ @@ -614,12 +599,14 @@ private static function compute_style_properties( $declarations, $styles ) { foreach ( $properties as $prop ) { $value = self::get_property_value( $styles, $prop['value'] ); - if ( ! empty( $value ) ) { - $declarations[] = array( - 'name' => $prop['name'], - 'value' => $value, - ); + if ( empty( $value ) ) { + continue; } + + $declarations[] = array( + 'name' => $prop['name'], + 'value' => $value, + ); } return $declarations; @@ -841,16 +828,62 @@ private function get_css_variables( $nodes ) { * @return string The new stylesheet. */ private function get_block_styles( $style_nodes, $setting_nodes ) { - $block_rules = ''; + $block_rules = self::ELEMENTS['link'] . '{color: var(--wp--style--color--link);}'; foreach ( $style_nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; } - $selector = $metadata['selector']; $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $selector = $metadata['selector']; $declarations = self::compute_style_properties( array(), $node ); - $block_rules .= self::to_ruleset( $selector, $declarations ); + + $is_link_element = self::is_link_element( $metadata['selector'] ); + if ( ! $is_link_element ) { + $block_rules .= self::to_ruleset( $selector, $declarations ); + } else { + /* + * To be removed when the user provided styles for link color + * no longer use the --wp--style--link-color variable. + * + * We need to: + * + * 1. For the color property, output: + * + * $selector_without_the_link_element_selector { + * --wp--style--color--link: value + * } + * + * 2. For the rest of the properties: + * + * $selector { + * other-prop: value; + * other-prop: value; + * } + * + * The reason for 1 is that user styles are attached to the block wrapper. + * If 1 targets the a element is going to have higher specificity + * and will overwrite the user preferences. + * + * Once the user styles are updated to output an `a` element instead + * this can be removed. + */ + $declarations_color = array(); + $declarations_other = array(); + foreach ( $declarations as $declaration ) { + if ( 'color' === $declaration['name'] ) { + $declarations_color[] = array( + 'name' => '--wp--style--color--link', + 'value' => $declaration['value'], + ); + } else { + $declarations_other[] = $declaration; + } + } + + $block_rules .= self::to_ruleset( $selector, $declarations_other ); + $block_rules .= self::to_ruleset( self::without_link_selector( $selector ), $declarations_color ); + } } $preset_rules = ''; @@ -963,17 +996,47 @@ private static function get_style_nodes( $theme_json, $selectors = array() ) { return $nodes; } - foreach ( $theme_json['styles'] as $name => $node ) { + // Top-level. + $nodes[] = array( + 'path' => array( 'styles' ), + 'selector' => self::ROOT_BLOCK_SELECTOR, + ); + + if ( isset( $theme_json['styles']['elements'] ) ) { + foreach ( $theme_json['styles']['elements'] as $element => $node ) { + $nodes[] = array( + 'path' => array( 'styles', 'elements', $element ), + 'selector' => self::ELEMENTS[ $element ], + ); + } + } + + // Blocks. + if ( ! isset( $theme_json['styles']['blocks'] ) ) { + return $nodes; + } + + foreach ( $theme_json['styles']['blocks'] as $name => $node ) { $selector = null; if ( isset( $selectors[ $name ]['selector'] ) ) { $selector = $selectors[ $name ]['selector']; } $nodes[] = array( - 'path' => array( 'styles', $name ), + 'path' => array( 'styles', 'blocks', $name ), 'selector' => $selector, ); + + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { + foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'selector' => $selectors[ $name ]['elements'][ $element ], + ); + } + } } + return $nodes; } @@ -1002,17 +1065,29 @@ private static function get_setting_nodes( $theme_json, $selectors = array() ) { return $nodes; } - foreach ( $theme_json['settings'] as $name => $node ) { + // Top-level. + $nodes[] = array( + 'path' => array( 'settings' ), + 'selector' => self::ROOT_BLOCK_SELECTOR, + ); + + // Calculate paths for blocks. + if ( ! isset( $theme_json['settings']['blocks'] ) ) { + return $nodes; + } + + foreach ( $theme_json['settings']['blocks'] as $name => $node ) { $selector = null; if ( isset( $selectors[ $name ]['selector'] ) ) { $selector = $selectors[ $name ]['selector']; } $nodes[] = array( - 'path' => array( 'settings', $name ), + 'path' => array( 'settings', 'blocks', $name ), 'selector' => $selector, ); } + return $nodes; } @@ -1139,13 +1214,24 @@ private static function remove_insecure_settings( $input ) { * Processes a style node and returns the same node * without the insecure styles. * - * @param array $input Node to process. + * @param array $input Node to process. + * @param string $selector Selector for the node. * * @return array */ - private static function remove_insecure_styles( $input ) { + private static function remove_insecure_styles( $input, $selector ) { $output = array(); $declarations = self::compute_style_properties( array(), $input ); + // To be removed once the user styles + // no longer use the --wp--style--color--link. + if ( self::is_link_element( $selector ) ) { + foreach ( $declarations as $index => $declaration ) { + if ( 'color' === $declaration['name'] ) { + $declarations[ $index ]['name'] = '--wp--style--color--link'; + } + } + } + foreach ( $declarations as $declaration ) { if ( self::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) { $property = self::to_property( $declaration['name'] ); @@ -1173,20 +1259,54 @@ private static function is_safe_css_declaration( $property_name, $property_value return ! empty( trim( $filtered ) ); } + /** + * Whether the selector contains a link element. + * + * @param string $selector The selector to check. + * + * @return boolean + */ + private static function is_link_element( $selector ) { + $result = true; + if ( false === stripos( $selector, self::ELEMENTS['link'] ) ) { + $result = false; + } + + return $result; + } + + /** + * Remove the link selector from the input. + * + * @param string $selector CSS selector to process. + * + * @return string + */ + private static function without_link_selector( $selector ) { + $result = str_ireplace( self::ELEMENTS['link'], '', $selector ); + + if ( '' === trim( $result ) ) { + return self::ROOT_BLOCK_SELECTOR; + } + + return $result; + } + /** * Removes insecure data from theme.json. */ public function remove_insecure_properties() { $sanitized = array(); - $style_nodes = self::get_style_nodes( $this->theme_json ); + $blocks_metadata = self::get_blocks_metadata(); + $style_nodes = self::get_style_nodes( $this->theme_json, $blocks_metadata ); foreach ( $style_nodes as $metadata ) { $input = _wp_array_get( $this->theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { continue; } - $output = self::remove_insecure_styles( $input ); + $output = self::remove_insecure_styles( $input, $metadata['selector'] ); if ( ! empty( $output ) ) { gutenberg_experimental_set( $sanitized, $metadata['path'], $output ); } @@ -1238,58 +1358,61 @@ public function get_raw_data() { * @return array Config that adheres to the theme.json schema. */ public static function get_from_editor_settings( $settings ) { - $theme_settings = array( 'settings' => array() ); + $theme_settings = array( + 'version' => self::LATEST_SCHEMA, + 'settings' => array(), + ); // Deprecated theme supports. if ( isset( $settings['disableCustomColors'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] = array(); + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color']['custom'] = ! $settings['disableCustomColors']; + $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; } if ( isset( $settings['disableCustomGradients'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] = array(); + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color']['customGradient'] = ! $settings['disableCustomGradients']; + $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; } if ( isset( $settings['disableCustomFontSizes'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] = array(); + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; + $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; } if ( isset( $settings['enableCustomLineHeight'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] = array(); + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; + $theme_settings['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; } if ( isset( $settings['enableCustomUnits'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing'] = array(); + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? + $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? array( 'px', 'em', 'rem', 'vh', 'vw' ) : $settings['enableCustomUnits']; } if ( isset( $settings['colors'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] = array(); + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color']['palette'] = $settings['colors']; + $theme_settings['settings']['color']['palette'] = $settings['colors']; } if ( isset( $settings['gradients'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] = array(); + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color']['gradients'] = $settings['gradients']; + $theme_settings['settings']['color']['gradients'] = $settings['gradients']; } if ( isset( $settings['fontSizes'] ) ) { @@ -1300,10 +1423,10 @@ public static function get_from_editor_settings( $settings ) { $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; } } - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography'] = array(); + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['typography']['fontSizes'] = $font_sizes; + $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; } // This allows to make the plugin work with WordPress 5.7 beta @@ -1311,18 +1434,18 @@ public static function get_from_editor_settings( $settings ) { // as soon as the minimum WordPress version for the plugin // is bumped to 5.7. if ( isset( $settings['enableCustomSpacing'] ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing'] = array(); + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['spacing']['customPadding'] = $settings['enableCustomSpacing']; + $theme_settings['settings']['spacing']['customPadding'] = $settings['enableCustomSpacing']; } // Things that didn't land in core yet, so didn't have a setting assigned. if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { - if ( ! isset( $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] ) ) { - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color'] = array(); + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); } - $theme_settings['settings'][ self::ALL_BLOCKS_NAME ]['color']['link'] = true; + $theme_settings['settings']['color']['link'] = true; } return $theme_settings; diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index 15cb1fc256702..7c0cf2e2401ef 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -1,227 +1,237 @@ { + "version": 1, "settings": { - "defaults": { - "color": { - "palette": [ - { - "name": "Black", - "slug": "black", - "color": "#000000" - }, - { - "name": "Cyan bluish gray", - "slug": "cyan-bluish-gray", - "color": "#abb8c3" - }, - { - "name": "White", - "slug": "white", - "color": "#ffffff" - }, - { - "name": "Pale pink", - "slug": "pale-pink", - "color": "#f78da7" - }, - { - "name": "Vivid red", - "slug": "vivid-red", - "color": "#cf2e2e" - }, - { - "name": "Luminous vivid orange", - "slug": "luminous-vivid-orange", - "color": "#ff6900" - }, - { - "name": "Luminous vivid amber", - "slug": "luminous-vivid-amber", - "color": "#fcb900" - }, - { - "name": "Light green cyan", - "slug": "light-green-cyan", - "color": "#7bdcb5" - }, - { - "name": "Vivid green cyan", - "slug": "vivid-green-cyan", - "color": "#00d084" - }, - { - "name": "Pale cyan blue", - "slug": "pale-cyan-blue", - "color": "#8ed1fc" - }, - { - "name": "Vivid cyan blue", - "slug": "vivid-cyan-blue", - "color": "#0693e3" - }, - { - "name": "Vivid purple", - "slug": "vivid-purple", - "color": "#9b51e0" - } - ], - "gradients": [ - { - "name": "Vivid cyan blue to vivid purple", - "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)", - "slug": "vivid-cyan-blue-to-vivid-purple" - }, - { - "name": "Light green cyan to vivid green cyan", - "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)", - "slug": "light-green-cyan-to-vivid-green-cyan" - }, - { - "name": "Luminous vivid amber to luminous vivid orange", - "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)", - "slug": "luminous-vivid-amber-to-luminous-vivid-orange" - }, - { - "name": "Luminous vivid orange to vivid red", - "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)", - "slug": "luminous-vivid-orange-to-vivid-red" - }, - { - "name": "Very light gray to cyan bluish gray", - "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)", - "slug": "very-light-gray-to-cyan-bluish-gray" - }, - { - "name": "Cool to warm spectrum", - "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)", - "slug": "cool-to-warm-spectrum" - }, - { - "name": "Blush light purple", - "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", - "slug": "blush-light-purple" - }, - { - "name": "Blush bordeaux", - "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", - "slug": "blush-bordeaux" - }, - { - "name": "Luminous dusk", - "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)", - "slug": "luminous-dusk" - }, - { - "name": "Pale ocean", - "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)", - "slug": "pale-ocean" - }, - { - "name": "Electric grass", - "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)", - "slug": "electric-grass" - }, - { - "name": "Midnight", - "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)", - "slug": "midnight" - } - ], - "duotone": [ - { - "name": "Dark grayscale" , - "colors": [ "#000000", "#7f7f7f" ], - "slug": "dark-grayscale" - }, - { - "name": "Grayscale" , - "colors": [ "#000000", "#ffffff" ], - "slug": "grayscale" - }, - { - "name": "Purple and yellow" , - "colors": [ "#8c00b7", "#fcff41" ], - "slug": "purple-yellow" - }, - { - "name": "Blue and red" , - "colors": [ "#000097", "#ff4747" ], - "slug": "blue-red" - }, - { - "name": "Midnight" , - "colors": [ "#000000", "#00a5ff" ], - "slug": "midnight" - }, - { - "name": "Magenta and yellow" , - "colors": [ "#c7005a", "#fff278" ], - "slug": "magenta-yellow" - }, - { - "name": "Purple and green" , - "colors": [ "#a60072", "#67ff66" ], - "slug": "purple-green" - }, - { - "name": "Blue and orange" , - "colors": [ "#1900d8", "#ffa96b" ], - "slug": "blue-orange" - } - ], - "custom": true, - "link": false, - "customGradient": true - }, - "typography": { - "dropCap": true, - "customFontSize": true, - "customLineHeight": false, - "customFontStyle": true, - "customFontWeight": true, - "customTextTransforms": true, - "customTextDecorations": true, - "fontSizes": [ - { - "name": "Small", - "slug": "small", - "size": "13px" - }, - { - "name": "Normal", - "slug": "normal", - "size": "16px" - }, - { - "name": "Medium", - "slug": "medium", - "size": "20px" - }, - { - "name": "Large", - "slug": "large", - "size": "36px" - }, - { - "name": "Huge", - "slug": "huge", - "size": "42px" - } - ] - }, - "spacing": { - "customPadding": false, - "units": [ "px", "em", "rem", "vh", "vw" ] - }, - "border": { - "customColor": false, - "customRadius": false, - "customStyle": false, - "customWidth": false - } + "color": { + "palette": [ + { + "name": "Black", + "slug": "black", + "color": "#000000" + }, + { + "name": "Cyan bluish gray", + "slug": "cyan-bluish-gray", + "color": "#abb8c3" + }, + { + "name": "White", + "slug": "white", + "color": "#ffffff" + }, + { + "name": "Pale pink", + "slug": "pale-pink", + "color": "#f78da7" + }, + { + "name": "Vivid red", + "slug": "vivid-red", + "color": "#cf2e2e" + }, + { + "name": "Luminous vivid orange", + "slug": "luminous-vivid-orange", + "color": "#ff6900" + }, + { + "name": "Luminous vivid amber", + "slug": "luminous-vivid-amber", + "color": "#fcb900" + }, + { + "name": "Light green cyan", + "slug": "light-green-cyan", + "color": "#7bdcb5" + }, + { + "name": "Vivid green cyan", + "slug": "vivid-green-cyan", + "color": "#00d084" + }, + { + "name": "Pale cyan blue", + "slug": "pale-cyan-blue", + "color": "#8ed1fc" + }, + { + "name": "Vivid cyan blue", + "slug": "vivid-cyan-blue", + "color": "#0693e3" + }, + { + "name": "Vivid purple", + "slug": "vivid-purple", + "color": "#9b51e0" + } + ], + "gradients": [ + { + "name": "Vivid cyan blue to vivid purple", + "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)", + "slug": "vivid-cyan-blue-to-vivid-purple" + }, + { + "name": "Light green cyan to vivid green cyan", + "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)", + "slug": "light-green-cyan-to-vivid-green-cyan" + }, + { + "name": "Luminous vivid amber to luminous vivid orange", + "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)", + "slug": "luminous-vivid-amber-to-luminous-vivid-orange" + }, + { + "name": "Luminous vivid orange to vivid red", + "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)", + "slug": "luminous-vivid-orange-to-vivid-red" + }, + { + "name": "Very light gray to cyan bluish gray", + "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)", + "slug": "very-light-gray-to-cyan-bluish-gray" + }, + { + "name": "Cool to warm spectrum", + "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)", + "slug": "cool-to-warm-spectrum" + }, + { + "name": "Blush light purple", + "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", + "slug": "blush-light-purple" + }, + { + "name": "Blush bordeaux", + "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", + "slug": "blush-bordeaux" + }, + { + "name": "Luminous dusk", + "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)", + "slug": "luminous-dusk" + }, + { + "name": "Pale ocean", + "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)", + "slug": "pale-ocean" + }, + { + "name": "Electric grass", + "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)", + "slug": "electric-grass" + }, + { + "name": "Midnight", + "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)", + "slug": "midnight" + } + ], + "duotone": [ + { + "name": "Dark grayscale" , + "colors": [ "#000000", "#7f7f7f" ], + "slug": "dark-grayscale" + }, + { + "name": "Grayscale" , + "colors": [ "#000000", "#ffffff" ], + "slug": "grayscale" + }, + { + "name": "Purple and yellow" , + "colors": [ "#8c00b7", "#fcff41" ], + "slug": "purple-yellow" + }, + { + "name": "Blue and red" , + "colors": [ "#000097", "#ff4747" ], + "slug": "blue-red" + }, + { + "name": "Midnight" , + "colors": [ "#000000", "#00a5ff" ], + "slug": "midnight" + }, + { + "name": "Magenta and yellow" , + "colors": [ "#c7005a", "#fff278" ], + "slug": "magenta-yellow" + }, + { + "name": "Purple and green" , + "colors": [ "#a60072", "#67ff66" ], + "slug": "purple-green" + }, + { + "name": "Blue and orange" , + "colors": [ "#1900d8", "#ffa96b" ], + "slug": "blue-orange" + } + ], + "custom": true, + "link": false, + "customGradient": true + }, + "typography": { + "dropCap": true, + "customFontSize": true, + "customLineHeight": false, + "customFontStyle": true, + "customFontWeight": true, + "customTextTransforms": true, + "customTextDecorations": true, + "fontSizes": [ + { + "name": "Small", + "slug": "small", + "size": "13px" + }, + { + "name": "Normal", + "slug": "normal", + "size": "16px" + }, + { + "name": "Medium", + "slug": "medium", + "size": "20px" + }, + { + "name": "Large", + "slug": "large", + "size": "36px" + }, + { + "name": "Huge", + "slug": "huge", + "size": "42px" + } + ] + }, + "spacing": { + "customPadding": false, + "units": [ "px", "em", "rem", "vh", "vw" ] }, - "core/button": { - "border": { - "customRadius": true + "border": { + "customColor": false, + "customRadius": false, + "customStyle": false, + "customWidth": false + }, + "blocks": { + "core/button": { + "border": { + "customRadius": true + } + } + } + }, + "styles": { + "elements": { + "link": { + "color": { + "text": "#00E" + } } } } diff --git a/lib/experimental-i18n-theme.json b/lib/experimental-i18n-theme.json index a9dc4c429ea47..24c55c17cd665 100644 --- a/lib/experimental-i18n-theme.json +++ b/lib/experimental-i18n-theme.json @@ -1,7 +1,6 @@ { "settings": { - "*": { - "typography": { + "typography": { "fontSizes": [ { "name": "Font size name" @@ -32,8 +31,8 @@ "name": "Text decoration name" } ] - }, - "color": { + }, + "color": { "palette": [ { "name": "Color name" @@ -49,6 +48,53 @@ "name": "Duotone name" } ] + }, + "blocks": { + "*": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontStyles": [ + { + "name": "Font style name" + } + ], + "fontWeights": [ + { + "name": "Font weight name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ], + "textTransforms": [ + { + "name": "Text transform name" + } + ], + "textDecorations": [ + { + "name": "Text decoration name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ] + } } } }, diff --git a/lib/global-styles.php b/lib/global-styles.php index b90175f4015a5..5e8afb2f98837 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -34,15 +34,6 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree, $type = 'al $stylesheet = $tree->get_stylesheet( $type ); - if ( ( 'all' === $type || 'block_styles' === $type ) && WP_Theme_JSON_Resolver::theme_has_support() ) { - // To support all themes, we added in the block-library stylesheet - // a style rule such as .has-link-color a { color: var(--wp--style--color--link, #00e); } - // so that existing link colors themes used didn't break. - // We add this here to make it work for themes that opt-in to theme.json - // In the future, we may do this differently. - $stylesheet .= 'a{color:var(--wp--style--color--link, #00e);}'; - } - if ( $can_use_cached ) { // Cache for a minute. // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. diff --git a/packages/block-editor/src/components/use-editor-feature/index.js b/packages/block-editor/src/components/use-editor-feature/index.js index 81ab094ec7f6c..1065859edc910 100644 --- a/packages/block-editor/src/components/use-editor-feature/index.js +++ b/packages/block-editor/src/components/use-editor-feature/index.js @@ -1,12 +1,11 @@ /** * External dependencies */ -import { get, isObject } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies */ -import { store as blocksStore } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -50,15 +49,6 @@ const deprecatedFlags = { 'spacing.customPadding': ( settings ) => settings.enableCustomSpacing, }; -function blockAttributesMatch( blockAttributes, attributes ) { - for ( const attribute in attributes ) { - if ( attributes[ attribute ] !== blockAttributes[ attribute ] ) { - return false; - } - } - return true; -} - /** * Hook that retrieves the setting for the given editor feature. * It works with nested objects using by finding the value at path. @@ -73,36 +63,16 @@ function blockAttributesMatch( blockAttributes, attributes ) { * ``` */ export default function useEditorFeature( featurePath ) { - const { name: blockName, clientId } = useBlockEditContext(); + const { name: blockName } = useBlockEditContext(); const setting = useSelect( ( select ) => { - const { getBlockAttributes, getSettings } = select( - blockEditorStore - ); - const settings = getSettings(); - const blockType = select( blocksStore ).getBlockType( blockName ); - - let context = blockName; - const selectors = get( blockType, [ - 'supports', - '__experimentalSelector', - ] ); - if ( clientId && isObject( selectors ) ) { - const blockAttributes = getBlockAttributes( clientId ) || {}; - for ( const contextSelector in selectors ) { - const { attributes } = selectors[ contextSelector ]; - if ( blockAttributesMatch( blockAttributes, attributes ) ) { - context = contextSelector; - break; - } - } - } + const settings = select( blockEditorStore ).getSettings(); // 1 - Use __experimental features, if available. // We cascade to the all value if the block one is not available. - const defaultsPath = `__experimentalFeatures.defaults.${ featurePath }`; - const blockPath = `__experimentalFeatures.${ context }.${ featurePath }`; + const defaultsPath = `__experimentalFeatures.${ featurePath }`; + const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ featurePath }`; const experimentalFeaturesResult = get( settings, blockPath ) ?? get( settings, defaultsPath ); if ( experimentalFeaturesResult !== undefined ) { @@ -123,7 +93,7 @@ export default function useEditorFeature( featurePath ) { // To remove when __experimentalFeatures are ported to core. return featurePath === 'typography.dropCap' ? true : undefined; }, - [ blockName, clientId, featurePath ] + [ blockName, featurePath ] ); return setting; diff --git a/packages/block-library/src/common.scss b/packages/block-library/src/common.scss index 4cd25f750d522..6cf9854943376 100644 --- a/packages/block-library/src/common.scss +++ b/packages/block-library/src/common.scss @@ -12,9 +12,6 @@ // Gradients @include gradient-colors(); - .has-link-color a:not(.wp-block-button__link) { - color: var(--wp--style--color--link, #00e); - } } // Font sizes. diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 0e32048da4e5b..fbfd5408d36b8 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -34,50 +34,7 @@ }, "fontSize": true, "lineHeight": true, - "__experimentalSelector": { - "core/heading/h1": { - "selector": "h1", - "title": "h1", - "attributes": { - "level": 1 - } - }, - "core/heading/h2": { - "selector": "h2", - "title": "h2", - "attributes": { - "level": 2 - } - }, - "core/heading/h3": { - "selector": "h3", - "title": "h3", - "attributes": { - "level": 3 - } - }, - "core/heading/h4": { - "selector": "h4", - "title": "h4", - "attributes": { - "level": 4 - } - }, - "core/heading/h5": { - "selector": "h5", - "title": "h5", - "attributes": { - "level": 5 - } - }, - "core/heading/h6": { - "selector": "h6", - "title": "h6", - "attributes": { - "level": 6 - } - } - }, + "__experimentalSelector": "h1,h2,h3,h4,h5,h6", "__unstablePasteTextInline": true }, "editorStyle": "wp-block-heading-editor", diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json index 5d8373e6f9257..21c1c52537db8 100644 --- a/packages/block-library/src/post-title/block.json +++ b/packages/block-library/src/post-title/block.json @@ -37,51 +37,7 @@ }, "fontSize": true, "lineHeight": true, - "__experimentalFontFamily": true, - "__experimentalSelector": { - "core/post-title/h1": { - "title": "h1", - "selector": "h1.wp-block-post-title", - "attributes": { - "level": 1 - } - }, - "core/post-title/h2": { - "title": "h2", - "selector": "h2.wp-block-post-title", - "attributes": { - "level": 2 - } - }, - "core/post-title/h3": { - "title": "h3", - "selector": "h3.wp-block-post-title", - "attributes": { - "level": 3 - } - }, - "core/post-title/h4": { - "title": "h4", - "selector": "h4.wp-block-post-title", - "attributes": { - "level": 4 - } - }, - "core/post-title/h5": { - "title": "h5", - "selector": "h5.wp-block-post-title", - "attributes": { - "level": 5 - } - }, - "core/post-title/h6": { - "title": "h6", - "selector": "h6.wp-block-post-title", - "attributes": { - "level": 6 - } - } - } + "__experimentalFontFamily": true }, "style": "wp-block-post-title" } diff --git a/packages/block-library/src/query-title/block.json b/packages/block-library/src/query-title/block.json index d3648269997bc..a78e9b77a58c7 100644 --- a/packages/block-library/src/query-title/block.json +++ b/packages/block-library/src/query-title/block.json @@ -25,51 +25,7 @@ }, "fontSize": true, "lineHeight": true, - "__experimentalFontFamily": true, - "__experimentalSelector": { - "core/query-title/h1": { - "title": "h1", - "selector": "h1.wp-block-query-title", - "attributes": { - "level": 1 - } - }, - "core/query-title/h2": { - "title": "h2", - "selector": "h2.wp-block-query-title", - "attributes": { - "level": 2 - } - }, - "core/query-title/h3": { - "title": "h3", - "selector": "h3.wp-block-query-title", - "attributes": { - "level": 3 - } - }, - "core/query-title/h4": { - "title": "h4", - "selector": "h4.wp-block-query-title", - "attributes": { - "level": 4 - } - }, - "core/query-title/h5": { - "title": "h5", - "selector": "h5.wp-block-query-title", - "attributes": { - "level": 5 - } - }, - "core/query-title/h6": { - "title": "h6", - "selector": "h6.wp-block-query-title", - "attributes": { - "level": 6 - } - } - } + "__experimentalFontFamily": true }, "editorStyle": "wp-block-query-title-editor" } diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index 4215022da6659..e276484dca72d 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -14,6 +14,7 @@ export const DEPRECATED_ENTRY_KEYS = [ export const __EXPERIMENTAL_STYLE_PROPERTY = { '--wp--style--color--link': { + valueGlobal: [ 'elements', 'link', 'color', 'text' ], value: [ 'color', 'link' ], support: [ 'color', 'link' ], }, diff --git a/packages/edit-site/src/components/editor/global-styles-provider.js b/packages/edit-site/src/components/editor/global-styles-provider.js index 443f8a3da8490..8da9dfc82fd27 100644 --- a/packages/edit-site/src/components/editor/global-styles-provider.js +++ b/packages/edit-site/src/components/editor/global-styles-provider.js @@ -24,18 +24,17 @@ import { useSelect, useDispatch } from '@wordpress/data'; * Internal dependencies */ import { - ALL_BLOCKS_NAME, - ALL_BLOCKS_SELECTOR, + ELEMENTS, ROOT_BLOCK_NAME, ROOT_BLOCK_SELECTOR, ROOT_BLOCK_SUPPORTS, getValueFromVariable, getPresetVariable, } from './utils'; -import getGlobalStyles from './global-styles-renderer'; +import { toCustomProperties, toStyles } from './global-styles-renderer'; import { store as editSiteStore } from '../../store'; -const EMPTY_CONTENT = { isGlobalStylesUserThemeJSON: true }; +const EMPTY_CONTENT = { isGlobalStylesUserThemeJSON: true, version: 1 }; const EMPTY_CONTENT_STRING = JSON.stringify( EMPTY_CONTENT ); const GlobalStylesContext = createContext( { @@ -82,47 +81,36 @@ const extractSupportKeys = ( supports ) => { return supportKeys; }; -const getContexts = ( blockTypes ) => { - const result = { - [ ROOT_BLOCK_NAME ]: { - selector: ROOT_BLOCK_SELECTOR, - supports: ROOT_BLOCK_SUPPORTS, - }, - [ ALL_BLOCKS_NAME ]: { - selector: ALL_BLOCKS_SELECTOR, - supports: [], // by being an empty array, the styles subtree will be ignored - }, - }; - - // Add contexts from block metadata. +const getBlockMetadata = ( blockTypes ) => { + const result = {}; + blockTypes.forEach( ( blockType ) => { - const blockName = blockType.name; - const blockSelector = blockType?.supports?.__experimentalSelector; + const name = blockType.name; const supports = extractSupportKeys( blockType?.supports ); const hasSupport = supports.length > 0; - if ( hasSupport && typeof blockSelector === 'string' ) { - result[ blockName ] = { - selector: blockSelector, - supports, - blockName, - }; - } else if ( hasSupport && typeof blockSelector === 'object' ) { - Object.keys( blockSelector ).forEach( ( key ) => { - result[ key ] = { - selector: blockSelector[ key ].selector, - supports, - blockName, - title: blockSelector[ key ].title, - attributes: blockSelector[ key ].attributes, - }; + if ( hasSupport ) { + const selector = + blockType?.supports?.__experimentalSelector ?? + '.wp-block-' + name.replace( 'core/', '' ).replace( '/', '-' ); + + const blockSelectors = selector.split( ',' ); + const elements = []; + Object.keys( ELEMENTS ).forEach( ( key ) => { + const elementSelector = []; + blockSelectors.forEach( ( blockSelector ) => { + elementSelector.push( + blockSelector + ' ' + ELEMENTS[ key ] + ); + } ); + elements[ key ] = elementSelector.join( ',' ); } ); - } else if ( hasSupport ) { - const suffix = blockName.replace( 'core/', '' ).replace( '/', '-' ); - result[ blockName ] = { - selector: '.wp-block-' + suffix, + + result[ name ] = { + name, + selector, supports, - blockName, + elements, }; } } ); @@ -140,13 +128,21 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { } ); const { updateSettings } = useDispatch( editSiteStore ); - const contexts = useMemo( () => getContexts( blockTypes ), [ blockTypes ] ); + const blocks = useMemo( () => getBlockMetadata( blockTypes ), [ + blockTypes, + ] ); const { __experimentalGlobalStylesBaseStyles: themeStyles } = settings; const { userStyles, mergedStyles } = useMemo( () => { let newUserStyles; try { newUserStyles = content ? JSON.parse( content ) : EMPTY_CONTENT; + + // At the moment, we ignore previous user config that + // is in a different version than the theme config. + if ( newUserStyles?.version !== baseStyles?.version ) { + newUserStyles = EMPTY_CONTENT; + } } catch ( e ) { /* eslint-disable no-console */ console.error( 'User data is not JSON' ); @@ -160,9 +156,9 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { if ( ! newUserStyles.isGlobalStylesUserThemeJSON ) { newUserStyles = EMPTY_CONTENT; } - // TODO: we probably want to check here that the shape is what we want - // This is, settings & styles are top-level keys, or perhaps a version. - // As to avoid merging trees that are different. + + // At this point, the version schema of the theme & user + // is the same, so we can merge them. const newMergedStyles = mergeWith( {}, baseStyles, @@ -178,33 +174,52 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { const nextValue = useMemo( () => ( { - contexts, - getSetting: ( context, path ) => - get( userStyles?.settings?.[ context ], path ), - setSetting: ( context, path, newValue ) => { + root: { + name: ROOT_BLOCK_NAME, + selector: ROOT_BLOCK_SELECTOR, + supports: ROOT_BLOCK_SUPPORTS, + elements: ELEMENTS, + }, + blocks, + getSetting: ( context, propertyPath ) => { + const path = + context === ROOT_BLOCK_NAME + ? propertyPath + : [ 'blocks', context, ...propertyPath ]; + get( userStyles?.settings, path ); + }, + setSetting: ( context, propertyPath, newValue ) => { const newContent = { ...userStyles }; - let contextSettings = newContent?.settings?.[ context ]; - if ( ! contextSettings ) { - contextSettings = {}; - set( newContent, [ 'settings', context ], contextSettings ); + const path = + context === ROOT_BLOCK_NAME + ? [ 'settings' ] + : [ 'settings', 'blocks', context ]; + + let newSettings = get( newContent, path ); + if ( ! newSettings ) { + newSettings = {}; + set( newContent, path, newSettings ); } - set( contextSettings, path, newValue ); + set( newSettings, propertyPath, newValue ); + setContent( JSON.stringify( newContent ) ); }, getStyle: ( context, propertyName, origin = 'merged' ) => { + const propertyPath = + STYLE_PROPERTY[ propertyName ].valueGlobal ?? + STYLE_PROPERTY[ propertyName ].value; + const path = + context === ROOT_BLOCK_NAME + ? propertyPath + : [ 'blocks', context, ...propertyPath ]; + if ( origin === 'theme' ) { - const value = get( - themeStyles?.styles?.[ context ], - STYLE_PROPERTY[ propertyName ].value - ); + const value = get( themeStyles?.styles, path ); return getValueFromVariable( themeStyles, context, value ); } if ( origin === 'user' ) { - const value = get( - userStyles?.styles?.[ context ], - STYLE_PROPERTY[ propertyName ].value - ); + const value = get( userStyles?.styles, path ); // We still need to use merged styles here because the // presets used to resolve user variable may be defined a @@ -212,22 +227,28 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { return getValueFromVariable( mergedStyles, context, value ); } - const value = get( - mergedStyles?.styles?.[ context ], - STYLE_PROPERTY[ propertyName ].value - ); + const value = get( mergedStyles?.styles, path ); return getValueFromVariable( mergedStyles, context, value ); }, setStyle: ( context, propertyName, newValue ) => { const newContent = { ...userStyles }; - let contextStyles = newContent?.styles?.[ context ]; - if ( ! contextStyles ) { - contextStyles = {}; - set( newContent, [ 'styles', context ], contextStyles ); + + const path = + ROOT_BLOCK_NAME === context + ? [ 'styles' ] + : [ 'styles', 'blocks', context ]; + const propertyPath = + STYLE_PROPERTY[ propertyName ].valueGlobal ?? + STYLE_PROPERTY[ propertyName ].value; + + let newStyles = get( newContent, path ); + if ( ! newStyles ) { + newStyles = {}; + set( newContent, path, newStyles ); } set( - contextStyles, - STYLE_PROPERTY[ propertyName ].value, + newStyles, + propertyPath, getPresetVariable( mergedStyles, context, @@ -235,6 +256,7 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { newValue ) ); + setContent( JSON.stringify( newContent ) ); }, } ), @@ -242,34 +264,28 @@ export default function GlobalStylesProvider( { children, baseStyles } ) { ); useEffect( () => { - const newStyles = settings.styles.filter( + const nonGlobalStyles = settings.styles.filter( ( style ) => ! style.isGlobalStyles ); + const customProperties = toCustomProperties( mergedStyles, blocks ); + const globalStyles = toStyles( mergedStyles, blocks ); updateSettings( { ...settings, styles: [ - ...newStyles, + ...nonGlobalStyles, { - css: getGlobalStyles( - contexts, - mergedStyles, - 'cssVariables' - ), + css: customProperties, isGlobalStyles: true, __experimentalNoWrapper: true, }, { - css: getGlobalStyles( - contexts, - mergedStyles, - 'blockStyles' - ), + css: globalStyles, isGlobalStyles: true, }, ], __experimentalFeatures: mergedStyles.settings, } ); - }, [ contexts, mergedStyles ] ); + }, [ blocks, mergedStyles ] ); return ( diff --git a/packages/edit-site/src/components/editor/global-styles-renderer.js b/packages/edit-site/src/components/editor/global-styles-renderer.js index 2b2236e58e369..054acf8468d6f 100644 --- a/packages/edit-site/src/components/editor/global-styles-renderer.js +++ b/packages/edit-site/src/components/editor/global-styles-renderer.js @@ -1,7 +1,17 @@ /** * External dependencies */ -import { capitalize, get, kebabCase, reduce, startsWith } from 'lodash'; +import { + capitalize, + forEach, + get, + isEmpty, + kebabCase, + pickBy, + reduce, + set, + startsWith, +} from 'lodash'; /** * WordPress dependencies @@ -11,7 +21,7 @@ import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY } from '@wordpress/bloc /** * Internal dependencies */ -import { LINK_COLOR_DECLARATION, PRESET_METADATA } from './utils'; +import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, ELEMENTS } from './utils'; function compileStyleValue( uncompiledValue ) { const VARIABLE_REFERENCE_PREFIX = 'var:'; @@ -34,7 +44,7 @@ function compileStyleValue( uncompiledValue ) { * * @return {Array} An array of style declarations. */ -function getBlockPresetsDeclarations( blockPresets = {} ) { +function getPresetsDeclarations( blockPresets = {} ) { return reduce( PRESET_METADATA, ( declarations, { path, valueKey, cssVarInfix } ) => { @@ -57,7 +67,7 @@ function getBlockPresetsDeclarations( blockPresets = {} ) { * @param {Object} blockPresets * @return {string} CSS declarations for the preset classes. */ -function getBlockPresetClasses( blockSelector, blockPresets = {} ) { +function getPresetsClasses( blockSelector, blockPresets = {} ) { return reduce( PRESET_METADATA, ( declarations, { path, valueKey, classes } ) => { @@ -103,13 +113,16 @@ function flattenTree( input = {}, prefix, token ) { * * @return {Array} An array of style declarations. */ -function getBlockStylesDeclarations( blockStyles = {} ) { +function getStylesDeclarations( blockStyles = {} ) { return reduce( STYLE_PROPERTY, - ( declarations, { value, properties }, key ) => { + ( declarations, { value, valueGlobal, properties }, key ) => { + const pathToValue = valueGlobal ?? value; if ( !! properties ) { properties.forEach( ( prop ) => { - if ( ! get( blockStyles, [ ...value, prop ], false ) ) { + if ( + ! get( blockStyles, [ ...pathToValue, prop ], false ) + ) { // Do not create a declaration // for sub-properties that don't have any value. return; @@ -119,17 +132,17 @@ function getBlockStylesDeclarations( blockStyles = {} ) { : kebabCase( `${ key }${ capitalize( prop ) }` ); declarations.push( `${ cssProperty }: ${ compileStyleValue( - get( blockStyles, [ ...value, prop ] ) + get( blockStyles, [ ...pathToValue, prop ] ) ) }` ); } ); - } else if ( get( blockStyles, value, false ) ) { + } else if ( get( blockStyles, pathToValue, false ) ) { const cssProperty = key.startsWith( '--' ) ? key : kebabCase( key ); declarations.push( `${ cssProperty }: ${ compileStyleValue( - get( blockStyles, value ) + get( blockStyles, pathToValue ) ) }` ); } @@ -140,57 +153,208 @@ function getBlockStylesDeclarations( blockStyles = {} ) { ); } -export default ( blockData, tree, type = 'all' ) => { - return reduce( - blockData, - ( styles, { selector }, context ) => { - if ( type === 'all' || type === 'cssVariables' ) { - const variableDeclarations = [ - ...getBlockPresetsDeclarations( - tree?.settings?.[ context ] - ), - ...flattenTree( - tree?.settings?.[ context ]?.custom, - '--wp--custom--', - '--' - ), - ]; - - if ( variableDeclarations.length > 0 ) { - styles.push( - `${ selector } { ${ variableDeclarations.join( - ';' - ) } }` - ); - } +export const getNodesWithStyles = ( tree, blockSelectors ) => { + const nodes = []; + + if ( ! tree?.styles ) { + return nodes; + } + + const pickStyleKeys = ( treeToPickFrom ) => + pickBy( treeToPickFrom, ( value, key ) => + [ 'border', 'color', 'spacing', 'typography' ].includes( key ) + ); + + // Top-level. + const styles = pickStyleKeys( tree.styles ); + if ( !! styles ) { + nodes.push( { + styles, + selector: ROOT_BLOCK_SELECTOR, + } ); + } + forEach( tree.styles?.elements, ( value, index ) => { + nodes.push( { + styles: value, + selector: ELEMENTS[ index ], + } ); + } ); + + // Iterate over blocks: they can have styles & elements. + forEach( tree.styles?.blocks, ( node, blockName ) => { + const blockStyles = pickStyleKeys( node ); + if ( !! blockStyles ) { + nodes.push( { + styles: blockStyles, + selector: blockSelectors[ blockName ].selector, + } ); + } + + forEach( node?.elements, ( value, elementName ) => { + nodes.push( { + styles: value, + selector: blockSelectors[ blockName ].elements[ elementName ], + } ); + } ); + } ); + + return nodes; +}; + +export const getNodesWithSettings = ( tree, blockSelectors ) => { + const nodes = []; + + if ( ! tree?.settings ) { + return nodes; + } + + const pickPresets = ( treeToPickFrom ) => { + const presets = {}; + PRESET_METADATA.forEach( ( { path } ) => { + const value = get( treeToPickFrom, path, false ); + if ( value !== false ) { + set( presets, path, value ); } - if ( type === 'all' || type === 'blockStyles' ) { - const blockStyleDeclarations = getBlockStylesDeclarations( - tree?.styles?.[ context ] - ); + } ); + return presets; + }; - if ( blockStyleDeclarations.length > 0 ) { - styles.push( - `${ selector } { ${ blockStyleDeclarations.join( - ';' - ) } }` - ); - } + // Top-level. + const presets = pickPresets( tree.settings ); + if ( ! isEmpty( presets ) ) { + nodes.push( { + presets, + custom: tree.settings?.custom, + selector: ROOT_BLOCK_SELECTOR, + } ); + } - const presetClasses = getBlockPresetClasses( - selector, - tree?.settings?.[ context ] - ); - if ( presetClasses ) { - styles.push( presetClasses ); - } + // Blocks. + forEach( tree.settings?.blocks, ( node, blockName ) => { + const blockPresets = pickPresets( node ); + if ( ! isEmpty( blockPresets ) ) { + nodes.push( { + presets: blockPresets, + custom: node.custom, + selector: blockSelectors[ blockName ].selector, + } ); + } + } ); + + return nodes; +}; + +export const toCustomProperties = ( tree, blockSelectors ) => { + const settings = getNodesWithSettings( tree, blockSelectors ); + + let ruleset = ''; + settings.forEach( ( { presets, custom, selector } ) => { + const declarations = getPresetsDeclarations( presets ); + const customProps = flattenTree( custom, '--wp--custom--', '--' ); + if ( customProps.length > 0 ) { + declarations.push( ...customProps ); + } + + if ( declarations.length > 0 ) { + ruleset = ruleset + `${ selector }{${ declarations.join( ';' ) };}`; + } + } ); + + return ruleset; +}; + +const containsLinkElement = ( selector ) => + selector.toLowerCase().includes( ELEMENTS.link ); +const withoutLinkSelector = ( selector ) => { + const newSelector = selector + .split( ',' ) + .map( ( individualSelector ) => + individualSelector.replace( ELEMENTS.link, '' ).trim() + ) + .join( ',' ); + + if ( '' === newSelector ) { + return ROOT_BLOCK_SELECTOR; + } + + return newSelector; +}; + +export const toStyles = ( tree, blockSelectors ) => { + const nodesWithStyles = getNodesWithStyles( tree, blockSelectors ); + const nodesWithSettings = getNodesWithSettings( tree, blockSelectors ); + + let ruleset = `${ ELEMENTS.link }{color: var(--wp--style--color--link);}`; + nodesWithStyles.forEach( ( { selector, styles } ) => { + const declarations = getStylesDeclarations( styles ); + + if ( declarations.length === 0 ) { + return; + } + + if ( ! containsLinkElement( selector ) ) { + ruleset = ruleset + `${ selector }{${ declarations.join( ';' ) };}`; + } else { + // To be removed when the user provided styles for link color + // no longer use the --wp--style--link-color variable. + // + // We need to: + // + // 1. For the color property, output: + // + // $selector_without_the_link_element_selector { + // --wp--style--color--link: value + // } + // + // 2. For the rest of the properties: + // + // $selector { + // other-prop: value; + // other-prop: value; + // } + // + // The reason for 1 is that user styles are attached to the block wrapper. + // If 1 targets the a element is going to have higher specificity + // and will overwrite the user preferences. + // + // Once the user styles are updated to output an `a` element instead + // this can be removed. + + const declarationsColor = declarations.filter( + ( declaration ) => declaration.split( ':' )[ 0 ] === 'color' + ); + const declarationsOther = declarations.filter( + ( declaration ) => declaration.split( ':' )[ 0 ] !== 'color' + ); + + if ( declarationsOther.length > 0 ) { + ruleset = + ruleset + + `${ selector }{${ declarationsOther.join( ';' ) };}`; } - return styles; - }, - // Can this be converted to a context, as the global context? - // See comment in the server. - type === 'all' || type === 'blockStyles' - ? [ LINK_COLOR_DECLARATION ] - : [] - ).join( '' ); + + if ( declarationsColor.length === 1 ) { + const value = declarationsColor[ 0 ].split( ':' )[ 1 ]; + ruleset = + ruleset + + `${ withoutLinkSelector( + selector + ) }{--wp--style--color--link:${ value };}`; + } + } + } ); + + nodesWithSettings.forEach( ( { selector, presets } ) => { + if ( ROOT_BLOCK_SELECTOR === selector ) { + // Do not add extra specificity for top-level classes. + selector = ''; + } + + const classes = getPresetsClasses( selector, presets ); + if ( ! isEmpty( classes ) ) { + ruleset = ruleset + classes; + } + } ); + + return ruleset; }; diff --git a/packages/edit-site/src/components/editor/test/global-styles-renderer.js b/packages/edit-site/src/components/editor/test/global-styles-renderer.js new file mode 100644 index 0000000000000..d6a8d2da02a48 --- /dev/null +++ b/packages/edit-site/src/components/editor/test/global-styles-renderer.js @@ -0,0 +1,322 @@ +/** + * Internal dependencies + */ +import { + getNodesWithSettings, + getNodesWithStyles, + toCustomProperties, + toStyles, +} from '../global-styles-renderer'; +import { ELEMENTS, ROOT_BLOCK_SELECTOR } from '../utils'; + +describe( 'global styles renderer', () => { + describe( 'getNodesWithStyles', () => { + it( 'should return the nodes with styles', () => { + const tree = { + styles: { + color: { + background: 'red', + text: 'red', + }, + blocks: { + 'core/heading': { + color: { + background: 'blue', + text: 'blue', + }, + elements: { + h1: { + typography: { + fontSize: '42px', + }, + }, + h2: { + typography: { + fontSize: '23px', + }, + }, + }, + }, + }, + elements: { + link: { + color: { + background: 'yellow', + text: 'yellow', + }, + }, + }, + }, + }; + const blockSelectors = { + 'core/heading': { + selector: 'h1,h2,h3,h4,h5,h6', + elements: { + link: 'h1 a,h2 a,h3 a,h4 a,h5 a,h6 a', + h1: 'h1', + h2: 'h2', + h3: 'h3', + h4: 'h4', + h5: 'h5', + h6: 'h6', + }, + }, + }; + expect( getNodesWithStyles( tree, blockSelectors ) ).toEqual( [ + { + styles: { + color: { + background: 'red', + text: 'red', + }, + }, + selector: ROOT_BLOCK_SELECTOR, + }, + { + styles: { + color: { + background: 'yellow', + text: 'yellow', + }, + }, + selector: ELEMENTS.link, + }, + { + styles: { + color: { + background: 'blue', + text: 'blue', + }, + }, + selector: 'h1,h2,h3,h4,h5,h6', + }, + { + styles: { + typography: { + fontSize: '42px', + }, + }, + selector: 'h1', + }, + { + styles: { + typography: { + fontSize: '23px', + }, + }, + selector: 'h2', + }, + ] ); + } ); + } ); + describe( 'getNodesWithSettings', () => { + it( 'should return nodes with settings', () => { + const tree = { + styles: { + color: { + background: 'red', + text: 'red', + }, + }, + settings: { + color: { + palette: [ + { name: 'White', slug: 'white', color: 'white' }, + { name: 'Black', slug: 'black', color: 'black' }, + ], + }, + blocks: { + 'core/paragraph': { + typography: { + fontSizes: [ + { + name: 'small', + slug: 'small', + size: '12px', + }, + { + name: 'medium', + slug: 'medium', + size: '23px', + }, + ], + }, + }, + }, + }, + }; + + const blockSelectors = { + 'core/paragraph': { + selector: 'p', + elements: { + link: 'p a', + h1: 'p h1', + h2: 'p h2', + h3: 'p h3', + h4: 'p h4', + h5: 'p h5', + h6: 'p h6', + }, + }, + }; + + expect( getNodesWithSettings( tree, blockSelectors ) ).toEqual( [ + { + presets: { + color: { + palette: [ + { + name: 'White', + slug: 'white', + color: 'white', + }, + { + name: 'Black', + slug: 'black', + color: 'black', + }, + ], + }, + }, + selector: ROOT_BLOCK_SELECTOR, + }, + { + presets: { + typography: { + fontSizes: [ + { + name: 'small', + slug: 'small', + size: '12px', + }, + { + name: 'medium', + slug: 'medium', + size: '23px', + }, + ], + }, + }, + selector: 'p', + }, + ] ); + } ); + } ); + + describe( 'toCustomProperties', () => { + it( 'should return a ruleset', () => { + const tree = { + settings: { + color: { + palette: [ + { name: 'White', slug: 'white', color: 'white' }, + { name: 'Black', slug: 'black', color: 'black' }, + ], + }, + custom: { + 'font-primary': 'value', + 'line-height': { + body: 1.7, + heading: 1.3, + }, + }, + blocks: { + 'core/heading': { + typography: { + fontSizes: [ + { + name: 'small', + slug: 'small', + size: '12px', + }, + { + name: 'medium', + slug: 'medium', + size: '23px', + }, + ], + }, + }, + }, + }, + }; + + const blockSelectors = { + 'core/heading': { + selector: 'h1,h2,h3,h4,h5,h6', + }, + }; + + expect( toCustomProperties( tree, blockSelectors ) ).toEqual( + 'body{--wp--preset--color--white: white;--wp--preset--color--black: black;--wp--custom--font-primary: value;--wp--custom--line-height--body: 1.7;--wp--custom--line-height--heading: 1.3;}h1,h2,h3,h4,h5,h6{--wp--preset--font-size--small: 12px;--wp--preset--font-size--medium: 23px;}' + ); + } ); + } ); + + describe( 'toStyles', () => { + it( 'should return a ruleset', () => { + const tree = { + settings: { + color: { + palette: [ + { name: 'White', slug: 'white', color: 'white' }, + { name: 'Black', slug: 'black', color: 'black' }, + ], + }, + }, + styles: { + color: { + background: 'red', + }, + elements: { + h1: { + typography: { + fontSize: '42px', + }, + }, + }, + blocks: { + 'core/heading': { + color: { + text: 'orange', + }, + elements: { + link: { + color: { + text: 'hotpink', + }, + }, + }, + }, + }, + }, + }; + + const blockSelectors = { + 'core/heading': { + selector: 'h1,h2,h3,h4,h5,h6', + elements: { + link: + 'h1 ' + + ELEMENTS.link + + ',h2 ' + + ELEMENTS.link + + ',h3 ' + + ELEMENTS.link + + ',h4 ' + + ELEMENTS.link + + ',h5 ' + + ELEMENTS.link + + ',h6 ' + + ELEMENTS.link, + }, + }, + }; + + expect( toStyles( tree, blockSelectors ) ).toEqual( + 'a:not(.wp-block-button_link){color: var(--wp--style--color--link);}body{background-color: red;}h1{font-size: 42px;}h1,h2,h3,h4,h5,h6{color: orange;}h1,h2,h3,h4,h5,h6{--wp--style--color--link: hotpink;}.has-white-color{color: white !important;}.has-white-background-color{background-color: white !important;}.has-white-border-color{border-color: white !important;}.has-black-color{color: black !important;}.has-black-background-color{background-color: black !important;}.has-black-border-color{border-color: black !important;}' + ); + } ); + } ); +} ); diff --git a/packages/edit-site/src/components/editor/utils.js b/packages/edit-site/src/components/editor/utils.js index b695c102ef1c6..54aa19320397b 100644 --- a/packages/edit-site/src/components/editor/utils.js +++ b/packages/edit-site/src/components/editor/utils.js @@ -12,8 +12,6 @@ import { useSelect } from '@wordpress/data'; import { store as editSiteStore } from '../../store'; /* Supporting data */ -export const ALL_BLOCKS_NAME = 'defaults'; -export const ALL_BLOCKS_SELECTOR = 'body'; export const ROOT_BLOCK_NAME = 'root'; export const ROOT_BLOCK_SELECTOR = 'body'; export const ROOT_BLOCK_SUPPORTS = [ @@ -29,6 +27,15 @@ export const ROOT_BLOCK_SUPPORTS = [ 'textDecoration', 'textTransform', ]; +export const ELEMENTS = { + link: 'a:not(.wp-block-button_link)', + h1: 'h1', + h2: 'h2', + h3: 'h3', + h4: 'h4', + h5: 'h5', + h6: 'h6', +}; export const PRESET_METADATA = [ { @@ -96,40 +103,43 @@ function getPresetMetadataFromStyleProperty( styleProperty ) { export const LINK_COLOR = '--wp--style--color--link'; export const LINK_COLOR_DECLARATION = `a { color: var(${ LINK_COLOR }, #00e); }`; -export function useEditorFeature( featurePath, blockName = ALL_BLOCKS_NAME ) { +export function useEditorFeature( featurePath, blockName = '' ) { const settings = useSelect( ( select ) => { return select( editSiteStore ).getSettings(); } ); - return ( - get( - settings, - `__experimentalFeatures.${ blockName }.${ featurePath }` - ) ?? - get( - settings, - `__experimentalFeatures.${ ALL_BLOCKS_NAME }.${ featurePath }` - ) - ); + const topLevelPath = `__experimentalFeatures.${ featurePath }`; + const blockPath = `__experimentalFeatures.blocks.${ blockName }.${ featurePath }`; + return get( settings, blockPath ) ?? get( settings, topLevelPath ); } -export function getPresetVariable( styles, blockName, propertyName, value ) { +export function getPresetVariable( styles, context, propertyName, value ) { if ( ! value ) { return value; } - const presetData = getPresetMetadataFromStyleProperty( propertyName ); - if ( ! presetData ) { + + const metadata = getPresetMetadataFromStyleProperty( propertyName ); + if ( ! metadata ) { + // The property doesn't have preset data + // so the value should be returned as it is. return value; } - const { valueKey, path, cssVarInfix } = presetData; - const presets = - get( styles, [ 'settings', blockName, ...path ] ) ?? - get( styles, [ 'settings', ALL_BLOCKS_NAME, ...path ] ); - const presetObject = find( presets, ( preset ) => { - return preset[ valueKey ] === value; - } ); + + const basePath = + ROOT_BLOCK_NAME === context + ? [ 'settings' ] + : [ 'settings', 'blocks', context ]; + const { valueKey, path: propertyPath, cssVarInfix } = metadata; + const presets = get( styles, [ ...basePath, ...propertyPath ] ); + const presetObject = find( + presets, + ( preset ) => preset[ valueKey ] === value + ); if ( ! presetObject ) { + // Value wasn't found in the presets, + // so it must be a custom value. return value; } + return `var:preset|${ cssVarInfix }|${ presetObject.slug }`; } @@ -140,31 +150,32 @@ function getValueFromPresetVariable( [ presetType, slug ] ) { presetType = camelCase( presetType ); - const presetData = getPresetMetadataFromStyleProperty( presetType ); - if ( ! presetData ) { + const metadata = getPresetMetadataFromStyleProperty( presetType ); + if ( ! metadata ) { return variable; } + const presets = - get( styles, [ 'settings', blockName, ...presetData.path ] ) ?? - get( styles, [ 'settings', ALL_BLOCKS_NAME, ...presetData.path ] ); + get( styles, [ 'settings', 'blocks', blockName, ...metadata.path ] ) ?? + get( styles, [ 'settings', ...metadata.path ] ); if ( ! presets ) { return variable; } - const presetObject = find( presets, ( preset ) => { - return preset.slug === slug; - } ); + + const presetObject = find( presets, ( preset ) => preset.slug === slug ); if ( presetObject ) { - const { valueKey } = presetData; + const { valueKey } = metadata; const result = presetObject[ valueKey ]; return getValueFromVariable( styles, blockName, result ); } + return variable; } function getValueFromCustomVariable( styles, blockName, variable, path ) { const result = - get( styles, [ 'settings', blockName, 'custom', ...path ] ) ?? - get( styles, [ 'settings', ALL_BLOCKS_NAME, 'custom', ...path ] ); + get( styles, [ 'settings', 'blocks', blockName, 'custom', ...path ] ) ?? + get( styles, [ 'settings', 'custom', ...path ] ); if ( ! result ) { return variable; } @@ -176,6 +187,7 @@ export function getValueFromVariable( styles, blockName, variable ) { if ( ! variable || ! isString( variable ) ) { return variable; } + let parsedVar; const INTERNAL_REFERENCE_PREFIX = 'var:'; const CSS_REFERENCE_PREFIX = 'var(--wp--'; @@ -192,6 +204,7 @@ export function getValueFromVariable( styles, blockName, variable ) { .slice( CSS_REFERENCE_PREFIX.length, -CSS_REFERENCE_SUFFIX.length ) .split( '--' ); } else { + // Value is raw. return variable; } diff --git a/packages/edit-site/src/components/sidebar/color-palette-panel.js b/packages/edit-site/src/components/sidebar/color-palette-panel.js index 3fce2f3d8d4c8..8fae2214124d7 100644 --- a/packages/edit-site/src/components/sidebar/color-palette-panel.js +++ b/packages/edit-site/src/components/sidebar/color-palette-panel.js @@ -13,7 +13,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { useEditorFeature, ALL_BLOCKS_NAME } from '../editor/utils'; +import { useEditorFeature } from '../editor/utils'; import { store as editSiteStore } from '../../store'; /** @@ -40,17 +40,12 @@ export default function ColorPalettePanel( { .__experimentalGlobalStylesBaseStyles; const basePalette = get( baseStyles, [ - contextName, - 'settings', - 'color', - 'palette', - ] ) ?? - get( baseStyles, [ - ALL_BLOCKS_NAME, 'settings', + 'blocks', + contextName, 'color', 'palette', - ] ); + ] ) ?? get( baseStyles, [ 'settings', 'color', 'palette' ] ); if ( ! basePalette ) { return EMPTY_ARRAY; } diff --git a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js index f0017eb703d05..7aea104e9d8a4 100644 --- a/packages/edit-site/src/components/sidebar/global-styles-sidebar.js +++ b/packages/edit-site/src/components/sidebar/global-styles-sidebar.js @@ -24,7 +24,6 @@ import { useGlobalStylesReset, } from '../editor/global-styles-provider'; import DefaultSidebar from './default-sidebar'; -import { ROOT_BLOCK_NAME } from '../editor/utils'; import { default as TypographyPanel, useHasTypographyPanel, @@ -96,34 +95,20 @@ function GlobalStylesPanel( { ); } -function getPanelTitle( context ) { - /* - * We use the block's name as the panel title. - * - * Some blocks (eg: core/heading) can represent different - * contexts (eg: core/heading/h1, core/heading/h2). - * For those, we attach the selector (h1) after the block's name. - * - * The title can't be accessed in the server, - * as it's translatable and the block.json doesn't - * have it yet. - */ - const blockType = getBlockType( context.blockName ); +function getPanelTitle( blockName ) { + const blockType = getBlockType( blockName ); + // Protect against blocks that aren't registered // eg: widget-area if ( blockType === undefined ) { - return blockType; + return blockName; } - let panelTitle = blockType.title; - if ( context?.title ) { - panelTitle += ` (${ context.title })`; - } - return panelTitle; + return blockType.title; } function GlobalStylesBlockPanels( { - contexts, + blocks, getStyle, setStyle, getSetting, @@ -132,27 +117,24 @@ function GlobalStylesBlockPanels( { const panels = useMemo( () => sortBy( - map( contexts, ( context, name ) => { + map( blocks, ( block, name ) => { return { - context, + block, name, - wrapperPanelTitle: getPanelTitle( context ), + wrapperPanelTitle: getPanelTitle( name ), }; } ), ( { wrapperPanelTitle } ) => wrapperPanelTitle ), - [ contexts ] + [ blocks ] ); - return map( panels, ( { context, name, wrapperPanelTitle } ) => { - if ( name === ROOT_BLOCK_NAME ) { - return null; - } + return map( panels, ( { block, name, wrapperPanelTitle } ) => { return ( array( 'settings', '*', 'typography', 'fontSizes' ), + 'path' => array( 'settings', 'typography', 'fontSizes' ), 'key' => 'name', 'context' => 'Font size name', ), array( - 'path' => array( 'settings', '*', 'typography', 'fontStyles' ), + 'path' => array( 'settings', 'typography', 'fontStyles' ), 'key' => 'name', 'context' => 'Font style name', ), array( - 'path' => array( 'settings', '*', 'typography', 'fontWeights' ), + 'path' => array( 'settings', 'typography', 'fontWeights' ), 'key' => 'name', 'context' => 'Font weight name', ), array( - 'path' => array( 'settings', '*', 'typography', 'fontFamilies' ), + 'path' => array( 'settings', 'typography', 'fontFamilies' ), 'key' => 'name', 'context' => 'Font family name', ), array( - 'path' => array( 'settings', '*', 'typography', 'textTransforms' ), + 'path' => array( 'settings', 'typography', 'textTransforms' ), 'key' => 'name', 'context' => 'Text transform name', ), array( - 'path' => array( 'settings', '*', 'typography', 'textDecorations' ), + 'path' => array( 'settings', 'typography', 'textDecorations' ), 'key' => 'name', 'context' => 'Text decoration name', ), array( - 'path' => array( 'settings', '*', 'color', 'palette' ), + 'path' => array( 'settings', 'color', 'palette' ), 'key' => 'name', 'context' => 'Color name', ), array( - 'path' => array( 'settings', '*', 'color', 'gradients' ), + 'path' => array( 'settings', 'color', 'gradients' ), 'key' => 'name', 'context' => 'Gradient name', ), array( - 'path' => array( 'settings', '*', 'color', 'duotone' ), + 'path' => array( 'settings', 'color', 'duotone' ), 'key' => 'name', 'context' => 'Duotone name', ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontSizes' ), + 'key' => 'name', + 'context' => 'Font size name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontStyles' ), + 'key' => 'name', + 'context' => 'Font style name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontWeights' ), + 'key' => 'name', + 'context' => 'Font weight name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontFamilies' ), + 'key' => 'name', + 'context' => 'Font family name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'textTransforms' ), + 'key' => 'name', + 'context' => 'Text transform name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'textDecorations' ), + 'key' => 'name', + 'context' => 'Text decoration name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'color', 'palette' ), + 'key' => 'name', + 'context' => 'Color name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'color', 'gradients' ), + 'key' => 'name', + 'context' => 'Gradient name', + ), array( 'path' => array( 'customTemplates' ), 'key' => 'title', @@ -112,22 +152,37 @@ function test_translations_are_applied() { $this->assertSame( wp_get_theme()->get( 'TextDomain' ), 'fse' ); $this->assertSame( - $actual->get_settings()['root']['color'], array( - 'palette' => array( - array( - 'slug' => 'light', - 'name' => 'Jasny', - 'color' => '#f5f7f9', + 'color' => array( + 'palette' => array( + array( + 'slug' => 'light', + 'name' => 'Jasny', + 'color' => '#f5f7f9', + ), + array( + 'slug' => 'dark', + 'name' => 'Ciemny', + 'color' => '#000', + ), ), - array( - 'slug' => 'dark', - 'name' => 'Ciemny', - 'color' => '#000', + 'custom' => false, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'light', + 'name' => 'Jasny', + 'color' => '#f5f7f9', + ), + ), + ), ), ), - 'custom' => false, - ) + ), + $actual->get_settings() ); $this->assertSame( $actual->get_custom_templates(), diff --git a/phpunit/class-wp-theme-json-schema-v0-test.php b/phpunit/class-wp-theme-json-schema-v0-test.php index d9840e4dcb304..c5b39199017c5 100644 --- a/phpunit/class-wp-theme-json-schema-v0-test.php +++ b/phpunit/class-wp-theme-json-schema-v0-test.php @@ -153,4 +153,325 @@ function test_parse() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + + function test_get_settings() { + $defaults = WP_Theme_JSON_Schema_V0::ALL_BLOCKS_NAME; + $root = WP_Theme_JSON_Schema_V0::ROOT_BLOCK_NAME; + $theme_json = new WP_Theme_JSON( + array( + 'settings' => array( + $defaults => array( + 'color' => array( + 'customGradient' => false, + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + ), + $root => array( + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + 'invalid/key' => 'value', + ), + 'core/heading/h1' => array( + 'color' => array( + 'customGradient' => false, + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + ), + 'core/heading/h2' => array( + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'core/post-title/h1' => array( + 'color' => array( + 'customGradient' => false, + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + ), + 'core/post-title/h2' => array( + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'core/query-title/h1' => array( + 'color' => array( + 'customGradient' => false, + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + ), + 'core/query-title/h2' => array( + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + ), + 'styles' => array( + $root => array( + 'color' => array( + 'link' => 'blue', + ), + ), + ), + ) + ); + + $actual = $theme_json->get_settings(); + + $expected = array( + 'color' => array( + 'custom' => false, + 'customGradient' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'customGradient' => false, + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'core/post-title' => array( + 'color' => array( + 'customGradient' => false, + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'core/query-title' => array( + 'color' => array( + 'customGradient' => false, + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_get_stylesheet() { + $root_name = WP_Theme_JSON_Schema_V0::ROOT_BLOCK_NAME; + $all_blocks_name = WP_Theme_JSON_Schema_V0::ALL_BLOCKS_NAME; + + $theme_json = new WP_Theme_JSON( + array( + 'settings' => array( + $all_blocks_name => array( + 'color' => array( + 'text' => 'value', + 'palette' => array( + array( + 'slug' => 'white', + 'color' => 'white', + ), + array( + 'slug' => 'black', + 'color' => 'black', + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + array( + 'slug' => 'small', + 'fontFamily' => '14px', + ), + array( + 'slug' => 'big', + 'fontFamily' => '41px', + ), + ), + ), + 'misc' => 'value', + ), + $root_name => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), + ), + ), + ), + 'core/group' => array( + 'custom' => array( + 'base-font' => 16, + 'line-height' => array( + 'small' => 1.2, + 'medium' => 1.4, + 'large' => 1.8, + ), + ), + ), + ), + 'styles' => array( + $root_name => array( + 'color' => array( + 'link' => '#111', + 'text' => 'var:preset|color|grey', + ), + 'misc' => 'value', + ), + 'core/group' => array( + 'color' => array( + 'link' => '#333', + ), + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + 'bottom' => '24px', + ), + ), + ), + 'core/heading/h1' => array( + 'color' => array( + 'link' => '#111', + ), + 'typography' => array( + 'fontSize' => '1em', + ), + ), + 'core/heading/h2' => array( + 'color' => array( + 'link' => '#222', + ), + 'typography' => array( + 'fontSize' => '2em', + ), + ), + 'core/post-title/h2' => array( + 'color' => array( + 'link' => '#222', + ), + 'typography' => array( + 'fontSize' => '2em', + ), + ), + 'core/post-title/h5' => array( + 'color' => array( + 'link' => '#555', + ), + 'typography' => array( + 'fontSize' => '5em', + ), + ), + 'core/query-title/h4' => array( + 'color' => array( + 'link' => '#444', + ), + 'typography' => array( + 'fontSize' => '4em', + ), + ), + 'core/query-title/h5' => array( + 'color' => array( + 'link' => '#555', + ), + 'typography' => array( + 'fontSize' => '5em', + ), + ), + ), + 'misc' => 'value', + ) + ); + + $this->assertEquals( + 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}a:not(.wp-block-button_link){color: var(--wp--style--color--link);}body{color: var(--wp--preset--color--grey);}body{--wp--style--color--link: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group {--wp--style--color--link: #333;}h1 ,h2 ,h3 ,h4 ,h5 ,h6 {--wp--style--color--link: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title {--wp--style--color--link: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title {--wp--style--color--link: #555;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', + $theme_json->get_stylesheet() + ); + $this->assertEquals( + 'a:not(.wp-block-button_link){color: var(--wp--style--color--link);}body{color: var(--wp--preset--color--grey);}body{--wp--style--color--link: #111;}h1{font-size: 1em;}h2{font-size: 2em;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group {--wp--style--color--link: #333;}h1 ,h2 ,h3 ,h4 ,h5 ,h6 {--wp--style--color--link: #222;}.wp-block-post-title{font-size: 5em;}.wp-block-post-title {--wp--style--color--link: #555;}.wp-block-query-title{font-size: 5em;}.wp-block-query-title {--wp--style--color--link: #555;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', + $theme_json->get_stylesheet( 'block_styles' ) + ); + $this->assertEquals( + 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}', + $theme_json->get_stylesheet( 'css_variables' ) + ); + } + } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 372f6c7a7ba55..3e7d51479aa63 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -9,96 +9,137 @@ class WP_Theme_JSON_Test extends WP_UnitTestCase { function test_get_settings() { - $root_name = WP_Theme_JSON::ROOT_BLOCK_NAME; - // See schema at WP_Theme_JSON::SCHEMA. $theme_json = new WP_Theme_JSON( array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'custom' => false, + 'color' => array( + 'custom' => false, + ), + 'invalid/key' => 'value', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), + 'invalid/key' => 'value', ), - 'invalid/key' => 'value', ), ), 'styles' => array( - $root_name => array( - 'color' => array( - 'link' => 'blue', - ), + 'color' => array( + 'link' => 'blue', ), ), ) ); - $result = $theme_json->get_settings(); + $actual = $theme_json->get_settings(); $expected = array( - $root_name => array( - 'color' => array( - 'custom' => false, + 'color' => array( + 'custom' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), ), ), ); - $this->assertEqualSetsWithIndex( $expected, $result ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } function test_get_stylesheet() { - $root_name = WP_Theme_JSON::ROOT_BLOCK_NAME; - $all_blocks_name = WP_Theme_JSON::ALL_BLOCKS_NAME; - $theme_json = new WP_Theme_JSON( array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $all_blocks_name => array( - 'color' => array( - 'text' => 'value', - 'palette' => array( - array( - 'slug' => 'grey', - 'color' => 'grey', - ), + 'color' => array( + 'text' => 'value', + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', ), ), - 'typography' => array( - 'fontFamilies' => array( - array( - 'slug' => 'small', - 'fontFamily' => '14px', - ), - array( - 'slug' => 'big', - 'fontFamily' => '41px', - ), + ), + 'typography' => array( + 'fontFamilies' => array( + array( + 'slug' => 'small', + 'fontFamily' => '14px', + ), + array( + 'slug' => 'big', + 'fontFamily' => '41px', ), ), - 'misc' => 'value', ), - 'core/group' => array( - 'custom' => array( - 'base-font' => 16, - 'line-height' => array( - 'small' => 1.2, - 'medium' => 1.4, - 'large' => 1.8, + 'misc' => 'value', + 'blocks' => array( + 'core/group' => array( + 'custom' => array( + 'base-font' => 16, + 'line-height' => array( + 'small' => 1.2, + 'medium' => 1.4, + 'large' => 1.8, + ), ), ), ), ), 'styles' => array( - $root_name => array( - 'color' => array( - 'link' => '#111', - 'text' => 'var:preset|color|grey', + 'color' => array( + 'text' => 'var:preset|color|grey', + ), + 'misc' => 'value', + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#111', + 'background' => '#333', + ), ), - 'misc' => 'value', ), - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '12px', - 'bottom' => '24px', + 'blocks' => array( + 'core/group' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#111', + ), + ), + ), + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + 'bottom' => '24px', + ), + ), + ), + 'core/heading' => array( + 'color' => array( + 'text' => '#123456', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#111', + 'background' => '#333', + ), + 'typography' => array( + 'fontSize' => '60px', + ), + ), + ), + ), + 'core/post-title' => array( + 'color' => array( + 'text' => '#123456', ), ), ), @@ -108,11 +149,11 @@ function test_get_stylesheet() { ); $this->assertEquals( - 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}body{--wp--style--color--link: #111;color: var(--wp--preset--color--grey);}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', + 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}a:not(.wp-block-button_link){color: var(--wp--style--color--link);}body{color: var(--wp--preset--color--grey);}a:not(.wp-block-button_link){background-color: #333;}body{--wp--style--color--link: #111;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group {--wp--style--color--link: #111;}h1,h2,h3,h4,h5,h6{color: #123456;}h1 a:not(.wp-block-button_link),h2 a:not(.wp-block-button_link),h3 a:not(.wp-block-button_link),h4 a:not(.wp-block-button_link),h5 a:not(.wp-block-button_link),h6 a:not(.wp-block-button_link){background-color: #333;font-size: 60px;}h1 ,h2 ,h3 ,h4 ,h5 ,h6 {--wp--style--color--link: #111;}.wp-block-post-title{color: #123456;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', $theme_json->get_stylesheet() ); $this->assertEquals( - 'body{--wp--style--color--link: #111;color: var(--wp--preset--color--grey);}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', + 'a:not(.wp-block-button_link){color: var(--wp--style--color--link);}body{color: var(--wp--preset--color--grey);}a:not(.wp-block-button_link){background-color: #333;}body{--wp--style--color--link: #111;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group {--wp--style--color--link: #111;}h1,h2,h3,h4,h5,h6{color: #123456;}h1 a:not(.wp-block-button_link),h2 a:not(.wp-block-button_link),h3 a:not(.wp-block-button_link),h4 a:not(.wp-block-button_link),h5 a:not(.wp-block-button_link),h6 a:not(.wp-block-button_link){background-color: #333;font-size: 60px;}h1 ,h2 ,h3 ,h4 ,h5 ,h6 {--wp--style--color--link: #111;}.wp-block-post-title{color: #123456;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', $theme_json->get_stylesheet( 'block_styles' ) ); $this->assertEquals( @@ -124,22 +165,27 @@ function test_get_stylesheet() { function test_get_stylesheet_preset_rules_come_after_block_rules() { $theme_json = new WP_Theme_JSON( array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'core/group' => array( - 'color' => array( - 'palette' => array( - array( - 'slug' => 'grey', - 'color' => 'grey', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', + ), ), ), ), ), ), 'styles' => array( - 'core/group' => array( - 'color' => array( - 'text' => 'red', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'text' => 'red', + ), ), ), ), @@ -147,11 +193,11 @@ function test_get_stylesheet_preset_rules_come_after_block_rules() { ); $this->assertEquals( - '.wp-block-group{--wp--preset--color--grey: grey;}.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}.wp-block-group.has-grey-border-color{border-color: grey !important;}', + '.wp-block-group{--wp--preset--color--grey: grey;}a:not(.wp-block-button_link){color: var(--wp--style--color--link);}.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}.wp-block-group.has-grey-border-color{border-color: grey !important;}', $theme_json->get_stylesheet() ); $this->assertEquals( - '.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}.wp-block-group.has-grey-border-color{border-color: grey !important;}', + 'a:not(.wp-block-button_link){color: var(--wp--style--color--link);}.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}.wp-block-group.has-grey-border-color{border-color: grey !important;}', $theme_json->get_stylesheet( 'block_styles' ) ); } @@ -159,27 +205,28 @@ function test_get_stylesheet_preset_rules_come_after_block_rules() { public function test_get_stylesheet_preset_values_are_marked_as_important() { $theme_json = new WP_Theme_JSON( array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'defaults' => array( - 'color' => array( - 'palette' => array( - array( - 'slug' => 'grey', - 'color' => 'grey', - ), + 'color' => array( + 'palette' => array( + array( + 'slug' => 'grey', + 'color' => 'grey', ), ), ), ), 'styles' => array( - 'core/post-title/h2' => array( - 'color' => array( - 'text' => 'red', - 'background' => 'blue', - ), - 'typography' => array( - 'fontSize' => '12px', - 'lineHeight' => '1.3', + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'red', + 'background' => 'blue', + ), + 'typography' => array( + 'fontSize' => '12px', + 'lineHeight' => '1.3', + ), ), ), ), @@ -187,91 +234,95 @@ public function test_get_stylesheet_preset_values_are_marked_as_important() { ); $this->assertEquals( - 'body{--wp--preset--color--grey: grey;}h2.wp-block-post-title{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', + 'body{--wp--preset--color--grey: grey;}a:not(.wp-block-button_link){color: var(--wp--style--color--link);}p{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}.has-grey-border-color{border-color: grey !important;}', $theme_json->get_stylesheet() ); } public function test_merge_incoming_data() { - $root_name = WP_Theme_JSON::ROOT_BLOCK_NAME; - $initial = array( + $initial = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'custom' => false, - 'palette' => array( - array( - 'slug' => 'red', - 'color' => 'red', - ), - array( - 'slug' => 'green', - 'color' => 'green', - ), + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + array( + 'slug' => 'green', + 'color' => 'green', ), ), ), - 'core/paragraph' => array( - 'color' => array( - 'custom' => false, + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), ), ), ), 'styles' => array( - $root_name => array( - 'typography' => array( - 'fontSize' => '12', - ), + 'typography' => array( + 'fontSize' => '12', ), ), ); $add_new_block = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'core/list' => array( - 'color' => array( - 'custom' => false, + 'blocks' => array( + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), ), ), ), 'styles' => array( - 'core/list' => array( - 'typography' => array( - 'fontSize' => '12', - ), - 'color' => array( - 'background' => 'brown', + 'blocks' => array( + 'core/list' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'background' => 'brown', + ), ), ), ), ); $add_key_in_settings = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'customGradient' => true, - ), + 'color' => array( + 'customGradient' => true, ), ), ); $update_key_in_settings = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'custom' => true, - ), + 'color' => array( + 'custom' => true, ), ), ); $add_styles = array( - 'styles' => array( - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '12px', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + ), ), ), ), @@ -279,11 +330,14 @@ public function test_merge_incoming_data() { ); $add_key_in_styles = array( - 'styles' => array( - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'bottom' => '12px', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'bottom' => '12px', + ), ), ), ), @@ -291,44 +345,46 @@ public function test_merge_incoming_data() { ); $add_invalid_context = array( - 'styles' => array( - 'core/para' => array( - 'typography' => array( - 'lineHeight' => '12', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/para' => array( + 'typography' => array( + 'lineHeight' => '12', + ), ), ), ), ); $update_presets = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'palette' => array( - array( - 'slug' => 'blue', - 'color' => 'blue', - ), + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', ), - 'gradients' => array( - array( - 'slug' => 'gradient', - 'gradient' => 'gradient', - ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', ), ), - 'typography' => array( - 'fontSizes' => array( - array( - 'slug' => 'fontSize', - 'size' => 'fontSize', - ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', ), - 'fontFamilies' => array( - array( - 'slug' => 'fontFamily', - 'fontFamily' => 'fontFamily', - ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', ), ), ), @@ -336,70 +392,71 @@ public function test_merge_incoming_data() { ); $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'custom' => true, - 'customGradient' => true, - 'palette' => array( - array( - 'slug' => 'blue', - 'color' => 'blue', - ), + 'color' => array( + 'custom' => true, + 'customGradient' => true, + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', ), - 'gradients' => array( - array( - 'slug' => 'gradient', - 'gradient' => 'gradient', - ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', ), ), - 'typography' => array( - 'fontSizes' => array( - array( - 'slug' => 'fontSize', - 'size' => 'fontSize', - ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', ), - 'fontFamilies' => array( - array( - 'slug' => 'fontFamily', - 'fontFamily' => 'fontFamily', - ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', ), ), ), - 'core/paragraph' => array( - 'color' => array( - 'custom' => false, + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), ), - ), - 'core/list' => array( - 'color' => array( - 'custom' => false, + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), ), ), ), 'styles' => array( - $root_name => array( - 'typography' => array( - 'fontSize' => '12', - ), + 'typography' => array( + 'fontSize' => '12', ), - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '12px', - 'bottom' => '12px', + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + 'bottom' => '12px', + ), ), ), - ), - 'core/list' => array( - 'typography' => array( - 'fontSize' => '12', - ), - 'color' => array( - 'background' => 'brown', + 'core/list' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'background' => 'brown', + ), ), ), ), @@ -413,53 +470,141 @@ public function test_merge_incoming_data() { $theme_json->merge( new WP_Theme_JSON( $add_key_in_styles ) ); $theme_json->merge( new WP_Theme_JSON( $add_invalid_context ) ); $theme_json->merge( new WP_Theme_JSON( $update_presets ) ); - $result = $theme_json->get_raw_data(); + $actual = $theme_json->get_raw_data(); - $this->assertEqualSetsWithIndex( $expected, $result ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } function test_remove_insecure_properties_removes_unsafe_styles() { $theme_json = new WP_Theme_JSON( array( - 'styles' => array( - 'core/group' => array( - 'color' => array( - 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', - 'text' => 'var:preset|color|dark-gray', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-red', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-pink', + 'background' => 'var:preset|color|dark-red', + ), ), ), - 'invalid/key' => array( - 'background' => 'green', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-gray', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'gradient' => 'url(\'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScxMCc+PHNjcmlwdD5hbGVydCgnb2snKTwvc2NyaXB0PjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZGllbnQnPjxzdG9wIG9mZnNldD0nMTAlJyBzdG9wLWNvbG9yPScjRjAwJy8+PHN0b3Agb2Zmc2V0PSc5MCUnIHN0b3AtY29sb3I9JyNmY2MnLz4gPC9saW5lYXJHcmFkaWVudD48cmVjdCBmaWxsPSd1cmwoI2dyYWRpZW50KScgeD0nMCcgeT0nMCcgd2lkdGg9JzEwMCUnIGhlaWdodD0nMTAwJScvPjwvc3ZnPg==\')', + 'text' => 'var:preset|color|dark-pink', + ), + ), + ), + ), + 'invalid/key' => array( + 'background' => 'green', + ), ), ), ), true ); $theme_json->remove_insecure_properties(); - $result = $theme_json->get_raw_data(); + $actual = $theme_json->get_raw_data(); $expected = array( - 'styles' => array( - 'core/group' => array( - 'color' => array( - 'text' => 'var:preset|color|dark-gray', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => 'var:preset|color|dark-red', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + // We should also allow links but at the moment we don't + // because they're transformed to --wp--style--color-link + // due to how user styles work. + // 'text' => 'var:preset|color|dark-pink', + // . + 'background' => 'var:preset|color|dark-red', + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'text' => 'var:preset|color|dark-gray', + ), + // We should also allow links but at the moment we don't + // because they're transformed to --wp--style--color-link + // due to how user styles work. + // 'elements' => array( + // 'link' => array( + // 'color' => array( + // 'text' => 'var:preset|color|dark-pink', + // ), + // ), + // ), + // . ), ), ), ); - $this->assertEqualSetsWithIndex( $expected, $result ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } function test_remove_insecure_properties_removes_unsafe_styles_sub_properties() { $theme_json = new WP_Theme_JSON( array( - 'styles' => array( - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '1px', - 'right' => '1px', - 'bottom' => 'var(--unsafe-var-y)', - 'left' => '1px', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '1px', + 'right' => '1px', + 'bottom' => 'var(--unsafe-var-toplevel)', + 'left' => '1px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '2px', + 'right' => '2px', + 'bottom' => 'var(--unsafe-var-elements)', + 'left' => '2px', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '3px', + 'right' => '3px', + 'bottom' => 'var(--unsafe-var-block)', + 'left' => '3px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '4px', + 'right' => '4px', + 'bottom' => 'var(--unsafe-var-block-elements)', + 'left' => '4px', + ), + ), + ), ), ), ), @@ -468,63 +613,62 @@ function test_remove_insecure_properties_removes_unsafe_styles_sub_properties() true ); $theme_json->remove_insecure_properties(); - $result = $theme_json->get_raw_data(); + $actual = $theme_json->get_raw_data(); $expected = array( - 'styles' => array( - 'core/group' => array( - 'spacing' => array( - 'padding' => array( - 'top' => '1px', - 'right' => '1px', - 'left' => '1px', + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '1px', + 'right' => '1px', + 'left' => '1px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '2px', + 'right' => '2px', + 'left' => '2px', + ), + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '3px', + 'right' => '3px', + 'left' => '3px', + ), + ), + 'elements' => array( + 'link' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '4px', + 'right' => '4px', + 'left' => '4px', + ), + ), + ), ), ), ), ), ); - $this->assertEqualSetsWithIndex( $expected, $result ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } function test_remove_insecure_properties_removes_non_preset_settings() { - $root_name = WP_Theme_JSON::ROOT_BLOCK_NAME; $theme_json = new WP_Theme_JSON( array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - $root_name => array( - 'color' => array( - 'custom' => true, - 'palette' => array( - array( - 'name' => 'Red', - 'slug' => 'red', - 'color' => '#ff0000', - ), - array( - 'name' => 'Green', - 'slug' => 'green', - 'color' => '#00ff00', - ), - array( - 'name' => 'Blue', - 'slug' => 'blue', - 'color' => '#0000ff', - ), - ), - ), - 'spacing' => array( - 'customPadding' => false, - ), - ), - ), - ), - true - ); - $theme_json->remove_insecure_properties(); - $result = $theme_json->get_raw_data(); - $expected = array( - 'settings' => array( - $root_name => array( - 'color' => array( + 'color' => array( + 'custom' => true, 'palette' => array( array( 'name' => 'Red', @@ -543,78 +687,114 @@ function test_remove_insecure_properties_removes_non_preset_settings() { ), ), ), + 'spacing' => array( + 'customPadding' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => true, + 'palette' => array( + array( + 'name' => 'Yellow', + 'slug' => 'yellow', + 'color' => '#ff0000', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#00ff00', + ), + array( + 'name' => 'Orange', + 'slug' => 'orange', + 'color' => '#0000ff', + ), + ), + ), + 'spacing' => array( + 'customPadding' => false, + ), + ), + ), ), ), + true ); - $this->assertEqualSetsWithIndex( $expected, $result ); - } - - function test_remove_insecure_properties_removes_unsafe_preset_settings() { - $root_name = WP_Theme_JSON::ROOT_BLOCK_NAME; - $theme_json = new WP_Theme_JSON( - array( - 'settings' => array( - $root_name => array( - 'color' => array( + $theme_json->remove_insecure_properties(); + $result = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Red', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'green', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => '#0000ff', + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( 'palette' => array( array( - 'name' => 'Red/>ok', - 'slug' => 'red', + 'name' => 'Yellow', + 'slug' => 'yellow', 'color' => '#ff0000', ), - array( - 'name' => 'Green', - 'slug' => 'a" attr', - 'color' => '#00ff00', - ), - array( - 'name' => 'Blue', - 'slug' => 'blue', - 'color' => 'var(--custom-v1)', - ), array( 'name' => 'Pink', 'slug' => 'pink', - 'color' => '#FFC0CB', - ), - ), - ), - 'typography' => array( - 'fontFamilies' => array( - array( - 'name' => 'Helvetica Arial/>test', - 'slug' => 'helvetica-arial', - 'fontFamily' => 'Helvetica Neue, Helvetica, Arial, sans-serif', - ), - array( - 'name' => 'Geneva', - 'slug' => 'geneva#asa', - 'fontFamily' => 'Geneva, Tahoma, Verdana, sans-serif', - ), - array( - 'name' => 'Cambria', - 'slug' => 'cambria', - 'fontFamily' => 'Cambria, Georgia, serif', + 'color' => '#00ff00', ), array( - 'name' => 'Helvetica Arial', - 'slug' => 'helvetica-arial', - 'fontFamily' => 'var(--custom-var-1)', + 'name' => 'Orange', + 'slug' => 'orange', + 'color' => '#0000ff', ), ), ), ), ), ), - true ); - $theme_json->remove_insecure_properties(); - $result = $theme_json->get_raw_data(); - $expected = array( - 'settings' => array( - $root_name => array( + $this->assertEqualSetsWithIndex( $expected, $result ); + } + + function test_remove_insecure_properties_removes_unsafe_preset_settings() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( 'color' => array( 'palette' => array( + array( + 'name' => 'Red/>ok', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'a" attr', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => 'var(--custom-v1)', + ), array( 'name' => 'Pink', 'slug' => 'pink', @@ -624,11 +804,93 @@ function test_remove_insecure_properties_removes_unsafe_preset_settings() { ), 'typography' => array( 'fontFamilies' => array( + array( + 'name' => 'Helvetica Arial/>test', + 'slug' => 'helvetica-arial', + 'fontFamily' => 'Helvetica Neue, Helvetica, Arial, sans-serif', + ), + array( + 'name' => 'Geneva', + 'slug' => 'geneva#asa', + 'fontFamily' => 'Geneva, Tahoma, Verdana, sans-serif', + ), array( 'name' => 'Cambria', 'slug' => 'cambria', 'fontFamily' => 'Cambria, Georgia, serif', ), + array( + 'name' => 'Helvetica Arial', + 'slug' => 'helvetica-arial', + 'fontFamily' => 'var(--custom-var-1)', + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Red/>ok', + 'slug' => 'red', + 'color' => '#ff0000', + ), + array( + 'name' => 'Green', + 'slug' => 'a" attr', + 'color' => '#00ff00', + ), + array( + 'name' => 'Blue', + 'slug' => 'blue', + 'color' => 'var(--custom-v1)', + ), + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + ), + ), + ), + ), + true + ); + $theme_json->remove_insecure_properties(); + $result = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + array( + 'name' => 'Cambria', + 'slug' => 'cambria', + 'fontFamily' => 'Cambria, Georgia, serif', + ), + ), + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pink', + 'slug' => 'pink', + 'color' => '#FFC0CB', + ), + ), ), ), ), @@ -640,11 +902,10 @@ function test_remove_insecure_properties_removes_unsafe_preset_settings() { function test_remove_insecure_properties_applies_safe_styles() { $theme_json = new WP_Theme_JSON( array( - 'styles' => array( - 'root' => array( - 'color' => array( - 'text' => '#abcabc ', // Trailing space. - ), + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => '#abcabc ', // Trailing space. ), ), ), @@ -653,11 +914,10 @@ function test_remove_insecure_properties_applies_safe_styles() { $theme_json->remove_insecure_properties(); $result = $theme_json->get_raw_data(); $expected = array( - 'styles' => array( - 'root' => array( - 'color' => array( - 'text' => '#abcabc ', - ), + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'text' => '#abcabc ', ), ), ); @@ -744,38 +1004,37 @@ function test_get_from_editor_settings() { ); $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'defaults' => array( - 'color' => array( - 'custom' => false, - 'customGradient' => false, - 'gradients' => array( - array( - 'slug' => 'gradient-slug', - 'name' => 'Gradient Name', - 'gradient' => 'gradientvalue', - ), - ), - 'palette' => array( - array( - 'slug' => 'color-slug', - 'name' => 'Color Name', - 'color' => 'colorvalue', - ), + 'color' => array( + 'custom' => false, + 'customGradient' => false, + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', ), ), - 'spacing' => array( - 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + 'palette' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), ), - 'typography' => array( - 'customFontSize' => false, - 'customLineHeight' => true, - 'fontSizes' => array( - array( - 'slug' => 'size-slug', - 'name' => 'Size Name', - 'size' => 'sizevalue', - ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + ), + 'typography' => array( + 'customFontSize' => false, + 'customLineHeight' => true, + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', ), ), ), @@ -818,19 +1077,18 @@ function test_get_editor_settings_no_theme_support() { ); $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'settings' => array( - 'defaults' => array( - 'color' => array( - 'custom' => true, - 'customGradient' => true, - ), - 'spacing' => array( - 'units' => false, - ), - 'typography' => array( - 'customFontSize' => true, - 'customLineHeight' => false, - ), + 'color' => array( + 'custom' => true, + 'customGradient' => true, + ), + 'spacing' => array( + 'units' => false, + ), + 'typography' => array( + 'customFontSize' => true, + 'customLineHeight' => false, ), ), ); @@ -841,7 +1099,10 @@ function test_get_editor_settings_no_theme_support() { } function test_get_editor_settings_blank() { - $expected = array( 'settings' => array() ); + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array(), + ); $actual = WP_Theme_JSON::get_from_editor_settings( array() ); $this->assertEqualSetsWithIndex( $expected, $actual ); @@ -858,7 +1119,7 @@ function test_get_editor_settings_custom_units_can_be_disabled() { $actual = WP_Theme_JSON::get_from_editor_settings( $input ); - $this->assertEqualSetsWithIndex( $expected, $actual['settings']['defaults']['spacing'] ); + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); } function test_get_editor_settings_custom_units_can_be_enabled() { @@ -872,7 +1133,7 @@ function test_get_editor_settings_custom_units_can_be_enabled() { $actual = WP_Theme_JSON::get_from_editor_settings( $input ); - $this->assertEqualSetsWithIndex( $expected, $actual['settings']['defaults']['spacing'] ); + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); } function test_get_editor_settings_custom_units_can_be_filtered() { @@ -886,7 +1147,7 @@ function test_get_editor_settings_custom_units_can_be_filtered() { $actual = WP_Theme_JSON::get_from_editor_settings( $input ); - $this->assertEqualSetsWithIndex( $expected, $actual['settings']['defaults']['spacing'] ); + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); } } diff --git a/phpunit/data/themedir1/fse/experimental-theme.json b/phpunit/data/themedir1/fse/experimental-theme.json index 3cfe4accc5beb..bda2c7646305c 100644 --- a/phpunit/data/themedir1/fse/experimental-theme.json +++ b/phpunit/data/themedir1/fse/experimental-theme.json @@ -1,20 +1,32 @@ { + "version": 1, "settings": { - "root": { - "color": { - "palette": [ - { - "slug": "light", - "name": "Light", - "color": "#f5f7f9" - }, - { - "slug": "dark", - "name": "Dark", - "color": "#000" - } - ], - "custom": false + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f5f7f9" + }, + { + "slug": "dark", + "name": "Dark", + "color": "#000" + } + ], + "custom": false + }, + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f5f7f9" + } + ] + } } } },