From 01f9bf10efd95449be27e89bf890d692f99d03ba Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:00:24 +1000 Subject: [PATCH 1/9] Remove editor-only selectors from API --- .../block-api/block-metadata.md | 35 ------ .../block-api/block-selectors.md | 33 +---- lib/class-wp-theme-json-gutenberg.php | 11 -- lib/compat/wordpress-6.3/blocks.php | 14 +-- .../get-global-styles-and-settings.php | 55 +------- packages/block-library/src/image/block.json | 3 - packages/blocks/src/api/registration.js | 16 +-- packages/blocks/src/api/test/registration.js | 23 ---- .../class-wp-get-block-css-selectors-test.php | 119 +----------------- phpunit/class-wp-theme-json-test.php | 2 +- schemas/json/block.json | 98 --------------- 11 files changed, 21 insertions(+), 388 deletions(-) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index d048cc78fede68..63219182dd1c2c 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -28,9 +28,6 @@ Starting in WordPress 5.8 release, we encourage using the `block.json` metadata "my-plugin/message": "message" }, "usesContext": [ "groupId" ], - "editorSelectors": { - "root": ".editor-styles-wrapper .wp-block-my-plugin-notice" - }, "selectors": { "root": ".wp-block-my-plugin-notice" }, @@ -385,38 +382,6 @@ See [the block context documentation](/docs/reference-guides/block-api/block-con } ``` -### Editor Selectors - -- Type: `object` -- Optional -- Localized: No -- Property: `editorSelectors` -- Default: `{}` -- Since: `WordPress 6.3.0` - -Any editor specific custom CSS selectors, keyed by `root`, feature, or -sub-feature, to be used when generating block styles for theme.json -(global styles) stylesheets in the editor. - -Editor only selectors override those defined within the `selectors` property. - -See the [the selectors documentation](/docs/reference-guides/block-api/block-selectors.md) for more details. - -```json -{ - "editorSelectors": { - "root": ".my-custom-block-selector", - "color": { - "text": ".my-custom-block-selector p" - }, - "typography": { - "root": ".my-custom-block-selector > h2", - "text-decoration": ".my-custom-block-selector > h2 span" - } - } -} -``` - ### Selectors - Type: `object` diff --git a/docs/reference-guides/block-api/block-selectors.md b/docs/reference-guides/block-api/block-selectors.md index 6560827bc54e29..9ce605782c628e 100644 --- a/docs/reference-guides/block-api/block-selectors.md +++ b/docs/reference-guides/block-api/block-selectors.md @@ -4,7 +4,7 @@ Block Selectors is the API that allows blocks to customize the CSS selector used when their styles are generated. A block may customize its CSS selectors at three levels: root, feature, and -subfeature. Each may also be overridden with editor-only selectors. +subfeature. ## Root Selector @@ -113,33 +113,4 @@ example. As the `color` feature also doesn't define a `root` selector, selector, `.my-custom-block-selector`. For a subfeature such as `typography.font-size`, it would fallback to its parent -feature's selector given that is present, i.e. `.my-custom-block-selector > h2`. - -## Editor-only Selectors - -There are scenarios in which a block might need different markup within the -editor compared to the frontend e.g. inline cropping of the Image block. Some -generated styles may then need to be applied to different, or multiple, -elements. - -Continuing with the Image cropping example, the image border styles need to also -be applied to the cropping area. If the selector for the cropping area is added -to the normal `selectors` config for the block, it would be output unnecessarily -on the frontend. - -To avoid this, and include the selector for the editor only, the selectors for the border feature can be -overridden via the `editorSelectors` config. - -### Example -```json -{ - ... - "selectors": { - "root": ".wp-block-image", - "border": ".wp-block-image img" - }, - "editorSelectors": { - "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area" - }, -} -``` +feature's selector given that is present, i.e. `.my-custom-block-selector > h2`. \ No newline at end of file diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index b0367bb5c779d4..e8e9d46ad9dc94 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3456,17 +3456,6 @@ protected static function get_root_block_selector( $block_type ) { */ protected static function get_block_selectors( $block_type, $root_selector ) { if ( ! empty( $block_type->selectors ) ) { - $in_editor = false; - - if ( function_exists( 'get_current_screen' ) ) { - $current_screen = get_current_screen(); - $in_editor = $current_screen && $current_screen->is_block_editor; - } - - if ( $in_editor && ! empty( $block_type->editor_selectors ) ) { - return array_merge( $block_type->selectors, $block_type->editor_selectors ); - } - return $block_type->selectors; } diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php index 8866edf5b95fda..5f017997f52b58 100644 --- a/lib/compat/wordpress-6.3/blocks.php +++ b/lib/compat/wordpress-6.3/blocks.php @@ -6,10 +6,10 @@ */ /** - * Ensure the selectors, and editorSelectors properties, set via block.json - * metadata, are included within the block type's settings. + * Ensure the selectors, set via block.json metadata, are included within the + * block type's settings. * - * Note: This should be removed when the minimum required WP version is >= 6.2. + * Note: This should be removed when the minimum required WP version is >= 6.3. * * @see https://github.com/WordPress/gutenberg/pull/46496 * @@ -18,15 +18,11 @@ * * @return array Filtered block type settings. */ -function gutenberg_add_selectors_properties_to_block_type_settings( $settings, $metadata ) { +function gutenberg_add_selectors_property_to_block_type_settings( $settings, $metadata ) { if ( ! isset( $settings['selectors'] ) && isset( $metadata['selectors'] ) ) { $settings['selectors'] = $metadata['selectors']; } - if ( ! isset( $settings['editor_selectors'] ) && isset( $metadata['editorSelectors'] ) ) { - $settings['editor_selectors'] = $metadata['editorSelectors']; - } - return $settings; } -add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_properties_to_block_type_settings', 10, 2 ); +add_filter( 'block_type_metadata_settings', 'gutenberg_add_selectors_property_to_block_type_settings', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 5b4ff3a5dd5cb2..244f5749cda2ba 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -23,27 +23,10 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f return null; } - $has_selectors = ! empty( $block_type->selectors ); - $use_editor_selectors = false; - - // Determine if we are in the editor and require editor selectors - // if they are available. - if ( function_exists( 'get_current_screen' ) ) { - $current_screen = get_current_screen(); - $use_editor_selectors = ! empty( $block_type->editor_selectors ) && $current_screen && $current_screen->is_block_editor; - } + $has_selectors = ! empty( $block_type->selectors ); // Duotone (No fallback selectors for Duotone). if ( 'filters.duotone' === $target || array( 'filters', 'duotone' ) === $target ) { - // Prefer editor selector if available. - $duotone_editor_selector = $use_editor_selectors - ? _wp_array_get( $block_type->editor_selectors, array( 'filters', 'duotone' ), null ) - : null; - - if ( $duotone_editor_selector ) { - return $duotone_editor_selector; - } - // If selectors API in use, only use it's value or null. if ( $has_selectors ) { return _wp_array_get( $block_type->selectors, array( 'filters', 'duotone' ), null ); @@ -59,10 +42,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f // feature selectors later on. $root_selector = null; - if ( $use_editor_selectors && isset( $block_type->editor_selectors['root'] ) ) { - // Prefer editor selectors if specified. - $root_selector = $block_type->editor_selectors['root']; - } elseif ( $has_selectors && isset( $block_type->selectors['root'] ) ) { + if ( $has_selectors && isset( $block_type->selectors['root'] ) ) { // Use the selectors API if available. $root_selector = $block_type->selectors['root']; } elseif ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) { @@ -89,28 +69,10 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f if ( 1 === count( $target ) ) { $fallback_selector = $fallback ? $root_selector : null; - // Look for selector under `feature.root`. - $path = array_merge( $target, array( 'root' ) ); - - // Use editor specific selector if available. - if ( $use_editor_selectors ) { - $feature_selector = _wp_array_get( $block_type->editor_selectors, $path, null ); - - if ( $feature_selector ) { - return $feature_selector; - } - - // Check if feature selector set via shorthand. - $feature_selector = _wp_array_get( $block_type->editor_selectors, $target, null ); - - // Only return if a selector was found. - if ( is_string( $feature_selector ) ) { - return $feature_selector; - } - } - // Prefer the selectors API if available. if ( $has_selectors ) { + // Look for selector under `feature.root`. + $path = array_merge( $target, array( 'root' ) ); $feature_selector = _wp_array_get( $block_type->selectors, $path, null ); if ( $feature_selector ) { @@ -158,15 +120,8 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f // This may fallback either to parent feature or root selector. $subfeature_selector = null; - // Use any explicit editor selector. Subfeature editor-only selectors - // will not fall back to the feature's editor specific selector if - // the normal selectors object contains a selector for the subfeature. - if ( $use_editor_selectors ) { - $subfeature_selector = _wp_array_get( $block_type->editor_selectors, $target, null ); - } - // Use selectors API if available. - if ( $has_selectors && ! $subfeature_selector ) { + if ( $has_selectors ) { $subfeature_selector = _wp_array_get( $block_type->selectors, $target, null ); } diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 2342aa44d67302..ddb33649d77ef9 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -102,9 +102,6 @@ } }, "selectors": { - "border": ".wp-block-image img" - }, - "editorSelectors": { "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area" }, "styles": [ diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index cf27e0b2f38299..597081f6203e03 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -169,17 +169,9 @@ export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { serverSideBlockDefinitions[ blockName ].ancestor = definitions[ blockName ].ancestor; } - // The `selectors` and `editorSelectors` props are not yet included - // in the server provided definitions. Polyfill it as well. This can - // be removed when the minimum supported WordPress is >= 6.3. - if ( - serverSideBlockDefinitions[ blockName ].editorSelectors === - undefined && - definitions[ blockName ].editorSelectors - ) { - serverSideBlockDefinitions[ blockName ].editorSelectors = - definitions[ blockName ].editorSelectors; - } + // The `selectors` prop is not yet included in the server provided + // definitions. Polyfill it as well. This can be removed when the + // minimum supported WordPress is >= 6.3. if ( serverSideBlockDefinitions[ blockName ].selectors === undefined && @@ -223,7 +215,6 @@ function getBlockSettingsFromMetadata( { textdomain, ...metadata } ) { 'providesContext', 'usesContext', 'selectors', - 'editorSelectors', 'supports', 'styles', 'example', @@ -312,7 +303,6 @@ export function registerBlockType( blockNameOrMetadata, settings ) { providesContext: {}, usesContext: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 8a3e9fad0d5b5b..dbb11e4d23001a 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -135,7 +135,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -283,7 +282,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -321,7 +319,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -355,7 +352,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -391,7 +387,6 @@ describe( 'blocks', () => { usesContext: [ 'textColor' ], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -429,7 +424,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -467,7 +461,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -485,7 +478,6 @@ describe( 'blocks', () => { unstable__bootstrapServerSideBlockDefinitions( { [ blockName ]: { selectors: { root: '.wp-block-custom-selector' }, - editorSelectors: { root: '.editor-only-selector' }, category: 'ignored', }, } ); @@ -505,7 +497,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: { root: '.wp-block-custom-selector' }, - editorSelectors: { root: '.editor-only-selector' }, supports: {}, styles: [], variations: [], @@ -575,7 +566,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -608,7 +598,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -655,7 +644,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -716,7 +704,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -744,7 +731,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -832,7 +818,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -973,7 +958,6 @@ describe( 'blocks', () => { providesContext: {}, usesContext: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [ @@ -1041,7 +1025,6 @@ describe( 'blocks', () => { providesContext: {}, usesContext: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [ { @@ -1108,7 +1091,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -1127,7 +1109,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -1208,7 +1189,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -1235,7 +1215,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -1269,7 +1248,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], @@ -1286,7 +1264,6 @@ describe( 'blocks', () => { usesContext: [], keywords: [], selectors: {}, - editorSelectors: {}, supports: {}, styles: [], variations: [], diff --git a/phpunit/class-wp-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index 47e42158ceea45..2ee4c7b3db28ee 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -16,30 +16,23 @@ public function set_up() { public function tear_down() { unregister_block_type( $this->test_block_name ); $this->test_block_name = null; - set_current_screen( '' ); parent::tear_down(); } - private function register_test_block( $name, $selectors = null, $supports = null, $editor_selectors = null ) { + private function register_test_block( $name, $selectors = null, $supports = null ) { $this->test_block_name = $name; return register_block_type( $this->test_block_name, array( - 'api_version' => 2, - 'attributes' => array(), - 'selectors' => $selectors, - 'editor_selectors' => $editor_selectors, - 'supports' => $supports, + 'api_version' => 2, + 'attributes' => array(), + 'selectors' => $selectors, + 'supports' => $supports, ) ); } - private function set_screen_to_block_editor() { - set_current_screen( 'edit-post' ); - get_current_screen()->is_block_editor( true ); - } - public function test_get_root_selector_via_selectors_api() { $block_type = self::register_test_block( 'test/block-with-selectors', @@ -335,106 +328,4 @@ public function test_string_targets_for_subfeatures() { $selector = wp_get_block_css_selector( $block_type, array( 'typography', 'fontSize' ) ); $this->assertEquals( '.found', $selector ); } - - public function test_editor_only_root_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-only-selectors', - array( 'root' => '.wp-custom-block-class' ), - null, - array( 'root' => '.editor-only.wp-custom-block-class' ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'root' ); - $this->assertEquals( '.editor-only.wp-custom-block-class', $selector ); - } - - public function test_editor_only_duotone_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-duotone-selector', - array( - 'filters' => array( 'duotone' => '.duotone-selector' ), - ), - null, - array( - 'filters' => array( 'duotone' => '.editor-duotone-selector' ), - ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'filters.duotone' ); - $this->assertEquals( '.editor-duotone-selector', $selector ); - } - - public function test_editor_only_feature_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-feature-selector', - array( 'typography' => array( 'root' => '.typography' ) ), - null, - array( 'typography' => array( 'root' => '.editor-typography' ) ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.editor-typography', $selector ); - } - - public function test_editor_only_feature_selector_shorthand() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-feature-selector', - array( 'typography' => '.typography' ), - null, - array( 'typography' => '.editor-typography' ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.editor-typography', $selector ); - } - - public function test_editor_only_subfeature_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-subfeature-selector', - array( 'typography' => array( 'fontSize' => '.font-size' ) ), - null, - array( 'typography' => array( 'fontSize' => '.editor-font-size' ) ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'typography.fontSize' ); - $this->assertEquals( '.editor-font-size', $selector ); - } - - public function test_non_editor_subfeature_does_not_fall_back_to_editor_only_feature_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-subfeature-selector', - array( 'typography' => array( 'fontSize' => '.font-size' ) ), - null, - array( 'typography' => '.editor-font-size' ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'typography.fontSize', true ); - $this->assertEquals( '.font-size', $selector ); - } - - public function test_unspecified_subfeature_falls_back_to_editor_only_feature_selector() { - self::set_screen_to_block_editor(); - - $block_type = self::register_test_block( - 'test/editor-subfeature-selector', - array( 'typography' => '.typography' ), - null, - array( 'typography' => '.editor-typography' ) - ); - - $selector = wp_get_block_css_selector( $block_type, 'typography.fontSize', true ); - $this->assertEquals( '.editor-typography', $selector ); - } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 104d87afc3e39b..eeb5940f44cd35 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -460,7 +460,7 @@ public function test_get_stylesheet() { ); $variables = '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;}'; - $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; + $styles = 'body { margin: 0;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}'; $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-small-font-family{font-family: var(--wp--preset--font-family--small) !important;}.has-big-font-family{font-family: var(--wp--preset--font-family--big) !important;}'; $all = $variables . $styles . $presets; diff --git a/schemas/json/block.json b/schemas/json/block.json index c0da49776b3f77..6023d829c5ecd0 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -526,104 +526,6 @@ } } }, - "editorSelectors": { - "type": "object", - "description": "Provides editor specific overrides to the custom CSS selectors defined within the block's selectors config.", - "properties": { - "root": { - "type": "string", - "description": "The primary CSS class to apply to the block within the editor. This replaces the `.wp-block-name` class if set." - }, - "border": { - "description": "Custom CSS selector used to generate rules for the block's theme.json border styles within the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "root": { "type": "string" }, - "color": { "type": "string" }, - "radius": { "type": "string" }, - "style": { "type": "string" }, - "width": { "type": "string" } - } - } - ] - }, - "color": { - "description": "Custom CSS selector used to generate rules for the block's theme.json color styles within the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "root": { "type": "string" }, - "text": { "type": "string" }, - "background": { "type": "string" } - } - } - ] - }, - "dimensions": { - "description": "Custom CSS selector used to generate rules for the block's theme.json dimensions styles within the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "root": { "type": "string" }, - "minHeight": { "type": "string" } - } - } - ] - }, - "spacing": { - "description": "Custom CSS selector used to generate rules for the block's theme.json spacing styles within the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "root": { "type": "string" }, - "blockGap": { "type": "string" }, - "padding": { "type": "string" }, - "margin": { "type": "string" } - } - } - ] - }, - "typography": { - "description": "Custom CSS selector used to generate rules for the block's theme.json typography styles within the editor.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "root": { "type": "string" }, - "fontFamily": { "type": "string" }, - "fontSize": { "type": "string" }, - "fontStyle": { "type": "string" }, - "fontWeight": { "type": "string" }, - "lineHeight": { "type": "string" }, - "letterSpacing": { "type": "string" }, - "textDecoration": { "type": "string" }, - "textTransform": { "type": "string" } - } - } - ] - } - } - }, "styles": { "type": "array", "description": "Block styles can be used to provide alternative styles to block. It works by adding a class name to the block’s wrapper. Using CSS, a theme developer can target the class name for the block style if it is selected.\n\nPlugins and Themes can also register custom block style for existing blocks.\n\nhttps://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles", From 7d96cf09dc60827a54614c7cdef019224867251f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:20:07 +1000 Subject: [PATCH 2/9] Duotone: Move from filters to filter key --- lib/class-wp-theme-json-gutenberg.php | 2 +- .../wordpress-6.3/get-global-styles-and-settings.php | 4 ++-- phpunit/class-wp-get-block-css-selectors-test.php | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index e8e9d46ad9dc94..162ef37634116e 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -870,7 +870,7 @@ protected static function get_blocks_metadata() { } // The block may or may not have a duotone selector. - $duotone_selector = wp_get_block_css_selector( $block_type, 'filters.duotone' ); + $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); if ( null !== $duotone_selector ) { static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector; } diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 244f5749cda2ba..f9b502c1218ca9 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -26,10 +26,10 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f $has_selectors = ! empty( $block_type->selectors ); // Duotone (No fallback selectors for Duotone). - if ( 'filters.duotone' === $target || array( 'filters', 'duotone' ) === $target ) { + if ( 'filter.duotone' === $target || array( 'filter', 'duotone' ) === $target ) { // If selectors API in use, only use it's value or null. if ( $has_selectors ) { - return _wp_array_get( $block_type->selectors, array( 'filters', 'duotone' ), null ); + return _wp_array_get( $block_type->selectors, array( 'filter', 'duotone' ), null ); } // Selectors API, not available, check for old experimental selector. diff --git a/phpunit/class-wp-get-block-css-selectors-test.php b/phpunit/class-wp-get-block-css-selectors-test.php index 2ee4c7b3db28ee..ba63f042a68dc0 100644 --- a/phpunit/class-wp-get-block-css-selectors-test.php +++ b/phpunit/class-wp-get-block-css-selectors-test.php @@ -80,12 +80,12 @@ public function test_get_duotone_selector_via_selectors_api() { $block_type = self::register_test_block( 'test/duotone-selector', array( - 'filters' => array( 'duotone' => '.duotone-selector' ), + 'filter' => array( 'duotone' => '.duotone-selector' ), ), null ); - $selector = wp_get_block_css_selector( $block_type, array( 'filters', 'duotone' ) ); + $selector = wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ) ); $this->assertEquals( '.duotone-selector', $selector ); } @@ -100,7 +100,7 @@ public function test_get_duotone_selector_via_experimental_property() { ) ); - $selector = wp_get_block_css_selector( $block_type, 'filters.duotone' ); + $selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); $this->assertEquals( '.experimental-duotone', $selector ); } @@ -111,7 +111,7 @@ public function test_no_duotone_selector_set() { null ); - $selector = wp_get_block_css_selector( $block_type, 'filters.duotone' ); + $selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); $this->assertEquals( null, $selector ); } From 57b649d64a921e0b46f7fcf20adeeea1a9ba21c6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:31:20 +1000 Subject: [PATCH 3/9] Make feature declaration method more generic --- lib/class-wp-theme-json-gutenberg.php | 33 +++++++++---------- .../get-global-styles-and-settings.php | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 162ef37634116e..ba2fcac768a47b 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2269,7 +2269,7 @@ public function get_styles_for_block( $block_metadata ) { $selector = $block_metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $feature_declarations = static::get_feature_declarations_for_block( $block_metadata, $node, $settings, $this->theme_json ); + $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node, $settings, $this->theme_json ); // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); @@ -3498,37 +3498,36 @@ protected static function get_block_element_selectors( $root_selector ) { return $element_selectors; } - /** - * Generates style declarations for the block's features e.g. color, border, - * typography etc, that have custom selectors in their block metadata. + * Generates style declarations for a node's features e.g. color, border, + * typography etc, that have custom selectors in their related block's + * metadata. * - * @param object $block_metadata The block's metadata containing selectors for - * features. - * @param object $block_node The merged theme.json node for the block. + * @param object $metadata The related block metadata containing selectors. + * @param object $node A merged theme.json node for block or variation. * @param object $settings The theme.json settings for the node. * @param object $theme_json The current theme.json config. * - * @return array The style declarations for the block's features with custom + * @return array The style declarations for the node's features with custom * selectors. */ - protected static function get_feature_declarations_for_block( $block_metadata, &$block_node, $settings, $theme_json ) { + protected static function get_feature_declarations_for_node( $metadata, &$node, $settings, $theme_json ) { $declarations = array(); - if ( ! isset( $block_metadata['selectors'] ) ) { + if ( ! isset( $metadata['selectors'] ) ) { return $declarations; } - foreach ( $block_metadata['selectors'] as $feature => $feature_selectors ) { + foreach ( $metadata['selectors'] as $feature => $feature_selectors ) { // Skip if this is the block's root selector or the block doesn't // have any styles for the feature. - if ( 'root' === $feature || empty( $block_node[ $feature ] ) ) { + if ( 'root' === $feature || empty( $node[ $feature ] ) ) { continue; } if ( is_array( $feature_selectors ) ) { foreach ( $feature_selectors as $subfeature => $subfeature_selector ) { - if ( 'root' === $subfeature || empty( $block_node[ $feature ][ $subfeature ] ) ) { + if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) { continue; } @@ -3536,7 +3535,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, & // to leverage existing `compute_style_properties` function. $subfeature_node = array( $feature => array( - $subfeature => $block_node[ $feature ][ $subfeature ], + $subfeature => $node[ $feature ][ $subfeature ], ), ); @@ -3555,7 +3554,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, & // Remove the subfeature from the block's node now its // styles will be included under its own selector not the // block's. - unset( $block_node[ $feature ][ $subfeature ] ); + unset( $node[ $feature ][ $subfeature ] ); } } @@ -3569,7 +3568,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, & // Create temporary node containing only the feature data // to leverage existing `compute_style_properties` function. - $feature_node = array( $feature => $block_node[ $feature ] ); + $feature_node = array( $feature => $node[ $feature ] ); // Generate the style declarations. $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $theme_json ); @@ -3587,7 +3586,7 @@ protected static function get_feature_declarations_for_block( $block_metadata, & // Remove the feature from the block's node now its styles // will be included under its own selector not the block's. - unset( $block_node[ $feature ] ); + unset( $node[ $feature ] ); } } diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index f9b502c1218ca9..c213e50d64ae7b 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -79,7 +79,7 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f return $feature_selector; } - // Check if feature selector set via shorthand. + // Check if feature selector is set via shorthand. $feature_selector = _wp_array_get( $block_type->selectors, $target, null ); return is_string( $feature_selector ) ? $feature_selector : $fallback_selector; From a624645133c0a573becc2353c3cf0a88887ea79f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:06:51 +1000 Subject: [PATCH 4/9] Update PHP style variations to use selectors API --- lib/class-wp-theme-json-gutenberg.php | 66 ++++++++++----------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index ba2fcac768a47b..3a3061018e02ca 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2275,51 +2275,33 @@ public function get_styles_for_block( $block_metadata ) { $style_variation_declarations = array(); if ( ! empty( $block_metadata['variations'] ) ) { foreach ( $block_metadata['variations'] as $style_variation ) { - $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); - $style_variation_selector = $style_variation['selector']; - - // If the block has feature selectors, generate the declarations for them within the current style variation. - if ( ! empty( $block_metadata['features'] ) ) { - $clean_style_variation_selector = trim( $style_variation_selector ); - foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) { - if ( empty( $style_variation_node[ $feature_name ] ) ) { - continue; - } - // If feature selector includes block classname, remove it but leave the whitespace in. - $shortened_feature_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $feature_selector ); - // Prepend the variation selector to the feature selector. - $split_feature_selectors = explode( ',', $shortened_feature_selector ); - $feature_selectors = array_map( - static function( $split_feature_selector ) use ( $clean_style_variation_selector ) { - return $clean_style_variation_selector . $split_feature_selector; - }, - $split_feature_selectors - ); - $combined_feature_selectors = implode( ',', $feature_selectors ); - - // Compute declarations for the feature. - $new_feature_declarations = static::compute_style_properties( array( $feature_name => $style_variation_node[ $feature_name ] ), $settings, null, $this->theme_json ); - - /* - * Merge new declarations with any that already exist for - * the feature selector. This may occur when multiple block - * support features use the same custom selector. - */ - if ( isset( $style_variation_declarations[ $combined_feature_selectors ] ) ) { - $style_variation_declarations[ $combined_feature_selectors ] = array_merge( $style_variation_declarations[ $combined_feature_selectors ], $new_feature_declarations ); - } else { - $style_variation_declarations[ $combined_feature_selectors ] = $new_feature_declarations; - } + $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); + $clean_style_variation_selector = trim( $style_variation['selector'] ); + + // Generate any feature/subfeature style declarations for the current style variation. + $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node, $settings, $this->theme_json ); + + // Combine selectors with style variation's selector and add to overall style variation declarations. + foreach ( $variation_declarations as $current_selector => $new_declarations ) { + // If current selector includes block classname, remove it but leave the whitespace in. + $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector ); + + // Prepend the variation selector to the current selector. + $split_selectors = explode( ',', $shortened_selector ); + $updated_selectors = array_map( + static function( $split_selector ) use ( $clean_style_variation_selector ) { + return $clean_style_variation_selector . $split_selector; + }, + $split_selectors + ); + $combined_selectors = implode( ',', $updated_selectors ); - /* - * Remove the feature from the variation's node now the - * styles will be included under the feature level selector. - */ - unset( $style_variation_node[ $feature_name ] ); - } + // Add the new declarations to the overall results under the modified selector. + $style_variation_declarations[ $combined_selectors ] = $new_declarations; } + // Compute declarations for remaining styles not covered by feature level selectors. - $style_variation_declarations[ $style_variation_selector ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); } } From 0bfa9a3a67a562a974dc9b8c4300e8f1b1ae7ec6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:48:21 +1000 Subject: [PATCH 5/9] Update useGlobalStylesOutput and JS side of selectors API --- .../global-styles/get-block-css-selector.js | 118 ++++++++++ .../src/components/global-styles/index.js | 1 + .../test/use-global-styles-output.js | 31 ++- .../global-styles/use-global-styles-output.js | 211 ++++++++++++------ 4 files changed, 292 insertions(+), 69 deletions(-) create mode 100644 packages/block-editor/src/components/global-styles/get-block-css-selector.js diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js new file mode 100644 index 00000000000000..4fde2d05cab964 --- /dev/null +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -0,0 +1,118 @@ +/** + * External dependencies + */ +import { get, isEmpty } from 'lodash'; + +/** + * Internal dependencies + */ +import { scopeSelector } from './utils'; + +export function getBlockCSSSelector( + blockType, + target = 'root', + options = {} +) { + if ( ! target ) { + return null; + } + + const { fallback = false } = options; + const { name, selectors, supports } = blockType; + + const hasSelectors = ! isEmpty( selectors ); + const path = Array.isArray( target ) ? target.join( '.' ) : target; + + // Duotone ( no fallback selectors for Duotone ). + if ( path === 'filter.duotone' ) { + // If selectors API in use, only use its value or null. + if ( hasSelectors ) { + return get( selectors, path, null ); + } + + // Selectors API, not available, check for old experimental selector. + return get( supports, 'color.__experimentalDuotone', null ); + } + + // Root selector. + + // Calculated before returning as it can be used as a fallback for feature + // selectors later on. + let rootSelector = null; + + if ( hasSelectors && selectors.root ) { + // Use the selectors API if available. + rootSelector = selectors?.root; + } else if ( supports?.__experimentalSelector ) { + // Use the old experimental selector supports property if set. + rootSelector = supports.__experimentalSelector; + } else { + // If no root selector found, generate default block class selector. + rootSelector = + '.wp-block-' + name.replace( 'core/', '' ).replace( '/', '-' ); + } + + // Return selector if it's the root target we are looking for. + if ( path === 'root' ) { + return rootSelector; + } + + // If target is not `root` or `duotone` we have a feature or subfeature + // as the target. If the target is a string convert to an array. + const pathArray = Array.isArray( target ) ? target : target.split( '.' ); + + // Feature selectors ( may fallback to root selector ); + if ( pathArray.length === 1 ) { + const fallbackSelector = fallback ? rootSelector : null; + + // Prefer the selectors API if available. + if ( hasSelectors ) { + // Get selector from either `feature.root` or shorthand path. + const featureSelector = + get( selectors, `${ path }.root`, null ) || + get( selectors, path, null ); + + // Return feature selector if found or any available fallback. + return featureSelector || fallbackSelector; + } + + // Try getting old experimental supports selector value. + const featureSelector = get( + supports, + `${ path }.__experimentalSelector`, + null + ); + + // If nothing to work with, provide fallback selector if available. + if ( ! featureSelector ) { + return fallbackSelector; + } + + // Scope the feature selector by the block's root selector. + return scopeSelector( rootSelector, featureSelector ); + } + + // Subfeature selector. + // This may fallback either to parent feature or root selector. + let subfeatureSelector; + + // Use selectors API if available. + if ( hasSelectors ) { + subfeatureSelector = get( selectors, path, null ); + } + + // Only return if we have a subfeature selector. + if ( subfeatureSelector ) { + return subfeatureSelector; + } + + // To this point we don't have a subfeature selector. If a fallback has been + // requested, remove subfeature from target path and return results of a + // call for the parent feature's selector. + if ( fallback ) { + return getBlockCSSSelector( blockType, pathArray[ 0 ], options ); + } + + // We tried. + return null; +} diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js index 5c344cea3924e8..97238de17a212c 100644 --- a/packages/block-editor/src/components/global-styles/index.js +++ b/packages/block-editor/src/components/global-styles/index.js @@ -4,6 +4,7 @@ export { useGlobalStyle, useSettingsForBlockElement, } from './hooks'; +export { getBlockCSSSelector } from './get-block-css-selector'; export { useGlobalStylesOutput } from './use-global-styles-output'; export { GlobalStylesContext } from './context'; export { diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index 1848906ed1fcde..a826c562044789 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -480,7 +480,7 @@ describe( 'global styles renderer', () => { expect( toStyles( tree, blockSelectors ) ).toEqual( 'body {margin: 0;}' + 'body{background-color: red;margin: 10px;padding: 10px;}a{color: blue;}a:hover{color: orange;}a:focus{color: orange;}h1{font-size: 42px;}.wp-block-group{margin-top: 10px;margin-right: 20px;margin-bottom: 30px;margin-left: 40px;padding-top: 11px;padding-right: 22px;padding-bottom: 33px;padding-left: 44px;}h1,h2,h3,h4,h5,h6{color: orange;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: hotpink;}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color: red;}h1 a:focus,h2 a:focus,h3 a:focus,h4 a:focus,h5 a:focus,h6 a:focus{color: red;}' + - '.wp-block-image img, .wp-block-image .wp-crop-area{border-radius: 9999px }.wp-block-image{color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' + + '.wp-block-image img, .wp-block-image .wp-crop-area{border-radius: 9999px}.wp-block-image{color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' + '.has-white-color{color: var(--wp--preset--color--white) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}h1.has-blue-color,h2.has-blue-color,h3.has-blue-color,h4.has-blue-color,h5.has-blue-color,h6.has-blue-color{color: var(--wp--preset--color--blue) !important;}h1.has-blue-background-color,h2.has-blue-background-color,h3.has-blue-background-color,h4.has-blue-background-color,h5.has-blue-background-color,h6.has-blue-background-color{background-color: var(--wp--preset--color--blue) !important;}h1.has-blue-border-color,h2.has-blue-border-color,h3.has-blue-border-color,h4.has-blue-border-color,h5.has-blue-border-color,h6.has-blue-border-color{border-color: var(--wp--preset--color--blue) !important;}' ); } ); @@ -668,6 +668,34 @@ describe( 'global styles renderer', () => { describe( 'getBlockSelectors', () => { it( 'should return block selectors data', () => { + const imageSelectors = { + root: '.my-image', + border: '.my-image img, .my-image .crop-area', + filter: { duotone: 'img' }, + }; + const imageBlock = { + name: 'core/image', + selectors: imageSelectors, + }; + const blockTypes = [ imageBlock ]; + + expect( getBlockSelectors( blockTypes, () => {} ) ).toEqual( { + 'core/image': { + name: imageBlock.name, + selector: imageSelectors.root, + duotoneSelector: imageSelectors.filter.duotone, + fallbackGapValue: undefined, + featureSelectors: { + root: '.my-image', + border: '.my-image img, .my-image .crop-area', + filter: { duotone: 'img' }, + }, + hasLayoutSupport: false, + }, + } ); + } ); + + it( 'should return block selectors data with old experimental selectors', () => { const imageSupports = { __experimentalBorder: { radius: true, @@ -688,6 +716,7 @@ describe( 'global styles renderer', () => { duotoneSelector: imageSupports.color.__experimentalDuotone, fallbackGapValue: undefined, featureSelectors: { + root: '.my-image', border: '.my-image img, .my-image .crop-area', }, hasLayoutSupport: false, diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index cce2b61f0786e7..20fa92e843f314 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -20,6 +20,7 @@ import { getCSSRules } from '@wordpress/style-engine'; * Internal dependencies */ import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, scopeSelector } from './utils'; +import { getBlockCSSSelector } from './get-block-css-selector'; import { getTypographyFontSizeValue } from './typography-utils'; import { GlobalStylesContext } from './context'; import { useGlobalSetting } from './hooks'; @@ -193,6 +194,89 @@ function concatFeatureVariationSelectorString( return combinedSelectors.join( ', ' ); } +/** + * Generate style declarations for a block's custom feature and subfeature + * selectors. + * + * NOTE: The passed `styles` object will be mutated by this function. + * + * @param {Object} selectors Custom selectors object for a block. + * @param {Object} styles A block's styles object. + * + * @return {Object} Style declarations. + */ +const getFeatureDeclarations = ( selectors, styles ) => { + const declarations = {}; + + Object.entries( selectors ).forEach( ( [ feature, selector ] ) => { + // We're only processing features/subfeatures that have styles. + if ( feature === 'root' || ! styles?.[ feature ] ) { + return; + } + + const isShorthand = typeof selector === 'string'; + + // If we have a selector object instead of shorthand process it. + if ( ! isShorthand ) { + Object.entries( selector ).forEach( + ( [ subfeature, subfeatureSelector ] ) => { + // Don't process root feature selector yet or any + // subfeature that doesn't have a style. + if ( + subfeature === 'root' || + ! styles?.[ feature ][ subfeature ] + ) { + return; + } + + // Create a temporary styles object and build + // declarations for subfeature. + const subfeatureStyles = { + [ feature ]: { + [ subfeature ]: styles[ feature ][ subfeature ], + }, + }; + const newDeclarations = + getStylesDeclarations( subfeatureStyles ); + + // Merge new declarations in with any others that + // share the same selector. + declarations[ subfeatureSelector ] = [ + ...( declarations[ subfeatureSelector ] || [] ), + ...newDeclarations, + ]; + + // Remove the subfeature's style now it will be + // included under its own selector not the block's. + delete styles[ feature ][ subfeature ]; + } + ); + } + + // Now subfeatures have been processed and removed, we can + // process root, or shorthand, feature selectors. + if ( isShorthand || selector.root ) { + const featureSelector = isShorthand ? selector : selector.root; + + // Create temporary style object and build declarations for feature. + const featureStyles = { [ feature ]: styles[ feature ] }; + const newDeclarations = getStylesDeclarations( featureStyles ); + + // Merge new declarations with any others that share the selector. + declarations[ featureSelector ] = [ + ...( declarations[ featureSelector ] || [] ), + ...newDeclarations, + ]; + + // Remove the feature from the block's styles now as it will be + // included under its own selector not the block's. + delete styles[ feature ]; + } + } ); + + return declarations; +}; + /** * Transform given style tree into a set of style declarations. * @@ -692,23 +776,16 @@ export const toStyles = ( // Process styles for block support features with custom feature level // CSS selectors set. if ( featureSelectors ) { - Object.entries( featureSelectors ).forEach( - ( [ featureName, featureSelector ] ) => { - if ( styles?.[ featureName ] ) { - const featureStyles = { - [ featureName ]: styles[ featureName ], - }; - const featureDeclarations = - getStylesDeclarations( featureStyles ); - delete styles[ featureName ]; - - if ( !! featureDeclarations.length ) { - ruleset = - ruleset + - `${ featureSelector }{${ featureDeclarations.join( - ';' - ) } }`; - } + const featureDeclarations = getFeatureDeclarations( + featureSelectors, + styles + ); + + Object.entries( featureDeclarations ).forEach( + ( [ cssSelector, declarations ] ) => { + if ( !! declarations.length ) { + const rules = declarations.join( ';' ); + ruleset = ruleset + `${ cssSelector }{${ rules }}`; } } ); @@ -720,43 +797,32 @@ export const toStyles = ( if ( styles?.variations?.[ styleVariationName ] ) { // If the block uses any custom selectors for block support, add those first. if ( featureSelectors ) { - Object.entries( featureSelectors ).forEach( - ( [ featureName, featureSelector ] ) => { - if ( - styles?.variations?.[ - styleVariationName - ]?.[ featureName ] - ) { - const featureStyles = { - [ featureName ]: - styles.variations[ - styleVariationName - ][ featureName ], - }; - const featureDeclarations = - getStylesDeclarations( - featureStyles + const featureDeclarations = + getFeatureDeclarations( + featureSelectors, + styles?.variations?.[ + styleVariationName + ] + ); + + Object.entries( featureDeclarations ).forEach( + ( [ baseSelector, declarations ] ) => { + if ( !! declarations.length ) { + const cssSelector = + concatFeatureVariationSelectorString( + baseSelector, + styleVariationSelector ); - delete styles.variations[ - styleVariationName - ][ featureName ]; - - if ( - !! featureDeclarations.length - ) { - ruleset = - ruleset + - `${ concatFeatureVariationSelectorString( - featureSelector, - styleVariationSelector - ) }{${ featureDeclarations.join( - ';' - ) } }`; - } + const rules = + declarations.join( ';' ); + ruleset = + ruleset + + `${ cssSelector }{${ rules }}`; } } ); } + // Otherwise add regular selectors. const styleVariationDeclarations = getStylesDeclarations( @@ -907,15 +973,37 @@ export function toSvgFilters( tree, blockSelectors ) { } ); } +const getSelectorsConfig = ( blockType, rootSelector ) => { + if ( ! isEmpty( blockType?.selectors ) ) { + return blockType.selectors; + } + + const config = { root: rootSelector }; + Object.entries( BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS ).forEach( + ( [ featureKey, featureName ] ) => { + const featureSelector = getBlockCSSSelector( + blockType, + featureKey + ); + + if ( featureSelector ) { + config[ featureName ] = featureSelector; + } + } + ); + + return config; +}; + export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { const result = {}; blockTypes.forEach( ( blockType ) => { const name = blockType.name; - const selector = - blockType?.supports?.__experimentalSelector ?? - '.wp-block-' + name.replace( 'core/', '' ).replace( '/', '-' ); - const duotoneSelector = - blockType?.supports?.color?.__experimentalDuotone ?? null; + const selector = getBlockCSSSelector( blockType, 'root' ); + const duotoneSelector = getBlockCSSSelector( + blockType, + 'filter.duotone' + ); const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout; const fallbackGapValue = blockType?.supports?.spacing?.blockGap?.__experimentalDefault; @@ -930,20 +1018,7 @@ export const getBlockSelectors = ( blockTypes, getBlockStyles ) => { } ); } // For each block support feature add any custom selectors. - const featureSelectors = {}; - Object.entries( BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS ).forEach( - ( [ featureKey, featureName ] ) => { - const featureSelector = - blockType?.supports?.[ featureKey ]?.__experimentalSelector; - - if ( featureSelector ) { - featureSelectors[ featureName ] = scopeSelector( - selector, - featureSelector - ); - } - } - ); + const featureSelectors = getSelectorsConfig( blockType, selector ); result[ name ] = { duotoneSelector, From be435853f191bc20e39ec21e9f1d61d116e461f3 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:47:39 +1000 Subject: [PATCH 6/9] Duotone: Update to use selectors API --- lib/class-wp-duotone-gutenberg.php | 10 ++++++---- packages/block-editor/src/hooks/duotone.js | 13 ++++++++----- packages/block-library/src/image/block.json | 7 +++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 090696fca0a146..7e1ad79c93bffb 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -235,9 +235,11 @@ public static function output_global_styles() { public static function render_duotone_support( $block_content, $block ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $duotone_support = false; - if ( $block_type && property_exists( $block_type, 'supports' ) ) { - $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); + $duotone_support = false; + $duotone_selector = null; + if ( $block_type ) { + $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' ); + $duotone_support = (bool) $duotone_selector; } // The block should have a duotone attribute or have duotone defined in its theme.json to be processed. @@ -308,7 +310,7 @@ public static function render_duotone_support( $block_content, $block ) { $filter_id = gutenberg_get_duotone_filter_id( array( 'slug' => $slug ) ); // Build the CSS selectors to which the filter will be applied. - $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_support ); + $selector = WP_Theme_JSON_Gutenberg::scope_selector( '.' . $filter_id, $duotone_selector ); // We only want to add the selector if we have it in the output already, essentially skipping 'unset'. if ( array_key_exists( $slug, self::$output ) ) { diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index b56d9f64b4e832..a05e188d507e1a 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -8,7 +8,11 @@ import namesPlugin from 'colord/plugins/names'; /** * WordPress dependencies */ -import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks'; +import { + getBlockSupport, + getBlockType, + hasBlockSupport, +} from '@wordpress/blocks'; import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { useMemo, useContext, createPortal } from '@wordpress/element'; @@ -269,10 +273,9 @@ function BlockDuotoneStyles( { name, duotoneStyle, id } ) { colors = getColorsFromDuotonePreset( colors, duotonePalette ); } - const duotoneSupportSelectors = getBlockSupport( - name, - 'color.__experimentalDuotone' - ); + const duotoneSupportSelectors = + getBlockType( name ).selectors?.filter?.duotone || + getBlockSupport( name, 'color.__experimentalDuotone' ); // Extra .editor-styles-wrapper specificity is needed in the editor // since we're not using inline styles to apply the filter. We need to diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index ddb33649d77ef9..b54358fc48d6a4 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -85,7 +85,7 @@ "supports": { "anchor": true, "color": { - "__experimentalDuotone": "img, .components-placeholder", + "__experimentalDuotone": true, "text": false, "background": false }, @@ -102,7 +102,10 @@ } }, "selectors": { - "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area" + "border": ".wp-block-image img, .wp-block-image .wp-block-image__crop-area", + "filter": { + "duotone": "img, .components-placeholder" + } }, "styles": [ { From 372c6f8bb9fb421dce99bb10aa16b34ba5174e5e Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 Mar 2023 10:41:53 +1000 Subject: [PATCH 7/9] Remove block old root selector function --- lib/class-wp-theme-json-gutenberg.php | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 3a3061018e02ca..abe4c808dba363 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3402,32 +3402,6 @@ public function set_spacing_sizes() { _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); } - /** - * Generates the root selector for a block. - * - * @param object $block_type The block type. - * @return string - */ - protected static function get_root_block_selector( $block_type ) { - // Prefer the selectors API if available. - if ( isset( $block_type->selectors ) && - isset( $block_type->selectors['root'] ) - ) { - return $block_type->selectors['root']; - } - - // Use the old experimental selector supports property if set. - if ( isset( $block_type->supports['__experimentalSelector'] ) && - is_string( $block_type->supports['__experimentalSelector'] ) ) { - return $block_type->supports['__experimentalSelector']; - } - - // Generate default block class selector. - $block_name = str_replace( '/', '-', str_replace( 'core/', '', $block_type->name ) ); - - return ".wp-block-{$block_name}"; - } - /** * Returns the selectors metadata for a block. * From 1ff98a84b5a3bcf027a71f855bf9b0bd876715e9 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:21:47 +1000 Subject: [PATCH 8/9] Add DocBlock to getBlockCSSSelector --- .../global-styles/get-block-css-selector.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/block-editor/src/components/global-styles/get-block-css-selector.js b/packages/block-editor/src/components/global-styles/get-block-css-selector.js index 4fde2d05cab964..db58709fe79aae 100644 --- a/packages/block-editor/src/components/global-styles/get-block-css-selector.js +++ b/packages/block-editor/src/components/global-styles/get-block-css-selector.js @@ -8,6 +8,17 @@ import { get, isEmpty } from 'lodash'; */ import { scopeSelector } from './utils'; +/** + * Determine the CSS selector for the block type and target provided, returning + * it if available. + * + * @param {import('@wordpress/blocks').Block} blockType The block's type. + * @param {string|string[]} target The desired selector's target e.g. `root`, delimited string, or array path. + * @param {Object} options Options object. + * @param {boolean} options.fallback Whether or not to fallback to broader selector. + * + * @return {?string} The CSS selector or `null` if no selector available. + */ export function getBlockCSSSelector( blockType, target = 'root', From 60e6148f00ec1f57434093f1e9ca8100cb675d00 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 Mar 2023 13:34:43 +1000 Subject: [PATCH 9/9] Clean up params for get_feature_declarations_for_node --- lib/class-wp-theme-json-gutenberg.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index abe4c808dba363..c7e1307f4a0bf0 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2269,7 +2269,7 @@ public function get_styles_for_block( $block_metadata ) { $selector = $block_metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node, $settings, $this->theme_json ); + $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node ); // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); @@ -2279,7 +2279,7 @@ public function get_styles_for_block( $block_metadata ) { $clean_style_variation_selector = trim( $style_variation['selector'] ); // Generate any feature/subfeature style declarations for the current style variation. - $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node, $settings, $this->theme_json ); + $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node ); // Combine selectors with style variation's selector and add to overall style variation declarations. foreach ( $variation_declarations as $current_selector => $new_declarations ) { @@ -3461,19 +3461,19 @@ protected static function get_block_element_selectors( $root_selector ) { * * @param object $metadata The related block metadata containing selectors. * @param object $node A merged theme.json node for block or variation. - * @param object $settings The theme.json settings for the node. - * @param object $theme_json The current theme.json config. * * @return array The style declarations for the node's features with custom * selectors. */ - protected static function get_feature_declarations_for_node( $metadata, &$node, $settings, $theme_json ) { + protected function get_feature_declarations_for_node( $metadata, &$node ) { $declarations = array(); if ( ! isset( $metadata['selectors'] ) ) { return $declarations; } + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + foreach ( $metadata['selectors'] as $feature => $feature_selectors ) { // Skip if this is the block's root selector or the block doesn't // have any styles for the feature. @@ -3496,7 +3496,7 @@ protected static function get_feature_declarations_for_node( $metadata, &$node, ); // Generate style declarations. - $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $theme_json ); + $new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json ); // Merge subfeature declarations into feature declarations. if ( isset( $declarations[ $subfeature_selector ] ) ) { @@ -3527,7 +3527,7 @@ protected static function get_feature_declarations_for_node( $metadata, &$node, $feature_node = array( $feature => $node[ $feature ] ); // Generate the style declarations. - $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $theme_json ); + $new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json ); // Merge new declarations with any that already exist for // the feature selector. This may occur when multiple block