diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index ed17b00f037dfe..5fcdec7969716e 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -28,6 +28,7 @@ function gutenberg_register_layout_support( $block_type ) { /** * Generates the CSS corresponding to the provided layout. * + * @param string $block_name Name of the current block. * @param string $selector CSS selector. * @param array $layout Layout object. The one that is passed has already checked the existence of default block layout. * @param boolean $has_block_gap_support Whether the theme has support for the block gap. @@ -36,9 +37,20 @@ function gutenberg_register_layout_support( $block_type ) { * * @return string CSS style. */ -function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false ) { +function gutenberg_get_layout_style( $block_name, $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false ) { $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; + // If there is no block-level value for blockGap, + // but a global styles value available for blockGap, + // use the latter. + if ( $has_block_gap_support && empty( $gap_value ) ) { + $block_global_styles = gutenberg_get_global_styles( array( 'blocks', $block_name, 'spacing' ) ); + + if ( isset( $block_global_styles['blockGap'] ) && ! empty( $block_global_styles['blockGap'] ) ) { + $gap_value = $block_global_styles['blockGap']; + } + } + $style = ''; if ( 'default' === $layout_type ) { $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : ''; @@ -176,7 +188,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { // If a block's block.json skips serialization for spacing or spacing.blockGap, // don't apply the user-defined value to the styles. $should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' ); - $style = gutenberg_get_layout_style( ".$class_name", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization ); + $style = gutenberg_get_layout_style( $block['blockName'], ".$class_name", $used_layout, $has_block_gap_support, $gap_value, $should_skip_gap_serialization ); // This assumes the hook only applies to blocks with a single wrapper. // I think this is a reasonable limitation for that particular hook. $content = preg_replace( diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php index e4d0aab1155e03..de973b0a574457 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php @@ -15,7 +15,6 @@ * @access private */ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 { - /** * The top-level keys a theme.json can have. * @@ -32,16 +31,62 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 { ); /** - * Returns the current theme's wanted patterns(slugs) to be - * registered from Pattern Directory. + * Sanitizes the input according to the schemas. * - * @return 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. */ - public function get_patterns() { - if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { - return $this->theme_json['patterns']; + protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { + $output = array(); + + if ( ! is_array( $input ) ) { + return $output; } - return array(); + + $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); + + // 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 ] = static::VALID_STYLES; + } + $schema_styles_blocks = array(); + $schema_settings_blocks = array(); + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = static::VALID_STYLES; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + $schema['styles'] = static::VALID_STYLES; + $schema['styles']['blocks'] = $schema_styles_blocks; + $schema['styles']['elements'] = $schema_styles_elements; + $schema['settings'] = static::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; + } + + if ( ! is_array( $input[ $subtree ] ) ) { + unset( $output[ $subtree ] ); + continue; + } + + $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); + + if ( empty( $result ) ) { + unset( $output[ $subtree ] ); + } else { + $output[ $subtree ] = $result; + } + } + + return $output; } /** @@ -62,6 +107,7 @@ public function get_patterns() { protected function get_block_classes( $style_nodes ) { $block_rules = ''; + foreach ( $style_nodes as $metadata ) { if ( null === $metadata['selector'] ) { continue; @@ -70,7 +116,7 @@ protected function get_block_classes( $style_nodes ) { $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $selector = $metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $declarations = static::compute_style_properties( $node, $settings ); + $declarations = static::compute_style_properties( $node, $settings, null, $metadata['selector'] ); // 1. Separate the ones who use the general selector // and the ones who use the duotone selector. @@ -119,6 +165,69 @@ protected function get_block_classes( $style_nodes ) { return $block_rules; } + /** + * Given a styles array, it extracts the style properties + * and adds them to the $declarations array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @param array $styles Styles to process. + * @param array $settings Theme settings. + * @param array $properties Properties metadata. + * @param string|null $selector Current selector. + * @return array Returns the modified $declarations. + */ + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $selector = null ) { + if ( null === $properties ) { + $properties = static::PROPERTIES_METADATA; + } + + $declarations = array(); + if ( empty( $styles ) ) { + return $declarations; + } + + foreach ( $properties as $css_property => $value_path ) { + // Some styles such as blockGap are only meant to be available at the top level (ROOT_BLOCK_SELECTOR), + // hence we only output styles at the top level. + if ( 'top' === _wp_array_get( self::VALID_STYLES, array( $value_path[0], $value_path[1] ), null ) && static::ROOT_BLOCK_SELECTOR !== $selector ) { + continue; + } + + $value = static::get_property_value( $styles, $value_path ); + + // Look up protected properties, keyed by value path. + // Skip protected properties that are explicitly set to `null`. + if ( is_array( $value_path ) ) { + $path_string = implode( '.', $value_path ); + if ( + array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null + ) { + continue; + } + } + + // Skip if empty and not "0" or value represents array of longhand values. + $has_missing_value = empty( $value ) && ! is_numeric( $value ); + if ( $has_missing_value || is_array( $value ) ) { + continue; + } + + $declarations[] = array( + 'name' => $css_property, + 'value' => $value, + ); + } + + return $declarations; + } + /** * Returns a valid theme.json for a theme. * Essentially, it flattens the preset data. @@ -162,4 +271,16 @@ public function get_data() { return $flattened_theme_json; } + /** + * Returns the current theme's wanted patterns(slugs) to be + * registered from Pattern Directory. + * + * @return array + */ + public function get_patterns() { + if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) { + return $this->theme_json['patterns']; + } + return array(); + } } diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index 47c6a320b8f5ab..db628050f0b47a 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -6,6 +6,7 @@ import { __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalBoxControl as BoxControl, + __experimentalUnitControl as UnitControl, __experimentalUseCustomUnits as useCustomUnits, } from '@wordpress/components'; import { __experimentalUseCustomSides as useCustomSides } from '@wordpress/block-editor'; @@ -20,8 +21,9 @@ const AXIAL_SIDES = [ 'horizontal', 'vertical' ]; export function useHasDimensionsPanel( name ) { const hasPadding = useHasPadding( name ); const hasMargin = useHasMargin( name ); + const hasGap = useHasGap( name ); - return hasPadding || hasMargin; + return hasPadding || hasMargin || hasGap; } function useHasPadding( name ) { @@ -38,6 +40,13 @@ function useHasMargin( name ) { return settings && supports.includes( 'margin' ); } +function useHasGap( name ) { + const supports = getSupportedGlobalStylesPanels( name ); + const [ settings ] = useSetting( 'spacing.blockGap', name ); + + return settings && supports.includes( '--wp--style--block-gap' ); +} + function filterValuesBySides( values, sides ) { if ( ! sides ) { // If no custom side configuration all sides are opted into by default. @@ -79,6 +88,7 @@ function splitStyleValue( value ) { export default function DimensionsPanel( { name } ) { const showPaddingControl = useHasPadding( name ); const showMarginControl = useHasMargin( name ); + const showGapControl = useHasGap( name ); const units = useCustomUnits( { availableUnits: useSetting( 'spacing.units', name )[ 0 ] || [ '%', @@ -118,9 +128,15 @@ export default function DimensionsPanel( { name } ) { const resetMarginValue = () => setMarginValues( {} ); const hasMarginValue = () => !! marginValues && Object.keys( marginValues ).length; + + const [ gapValue, setGapValue ] = useStyle( 'spacing.blockGap', name ); + const resetGapValue = () => setGapValue( undefined ); + const hasGapValue = () => !! gapValue; + const resetAll = () => { resetPaddingValue(); resetMarginValue(); + resetGapValue(); }; return ( @@ -161,6 +177,23 @@ export default function DimensionsPanel( { name } ) { /> ) } + { showGapControl && ( + + + + ) } ); }