diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 6d0c02e8d27ed3..bb2ce78584b2bc 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -83,6 +83,9 @@ Settings related to spacing. | margin | boolean | false | | | padding | boolean | false | | | units | array | px,em,rem,vh,vw,% | | +| customSpacingSize | boolean | true | | +| spacingSizes | array | | name, size, slug | +| spacingScale | object | | | --- diff --git a/lib/compat/wordpress-6.0/block-editor-settings.php b/lib/compat/wordpress-6.0/block-editor-settings.php index 9030998aed250f..21f1f05bb45f1f 100644 --- a/lib/compat/wordpress-6.0/block-editor-settings.php +++ b/lib/compat/wordpress-6.0/block-editor-settings.php @@ -47,149 +47,3 @@ function gutenberg_is_global_styles_in_5_9( $style ) { return false; } - -/** - * Adds styles and __experimentalFeatures to the block editor settings. - * - * @param array $settings Existing block editor settings. - * - * @return array New block editor settings. - */ -function gutenberg_get_block_editor_settings( $settings ) { - // Set what is the context for this data request. - $context = 'other'; - if ( - defined( 'REST_REQUEST' ) && - REST_REQUEST && - isset( $_GET['context'] ) && - 'mobile' === $_GET['context'] - ) { - $context = 'mobile'; - } - - if ( 'other' === $context ) { - global $wp_version; - $is_wp_5_8 = version_compare( $wp_version, '5.8', '>=' ) && version_compare( $wp_version, '5.9', '<' ); - $is_wp_5_9 = version_compare( $wp_version, '5.9', '>=' ) && version_compare( $wp_version, '6.0-beta1', '<' ); - $is_wp_6_0 = version_compare( $wp_version, '6.0-beta1', '>=' ); - - // Make sure the styles array exists. - // In some contexts, like the navigation editor, it doesn't. - if ( ! isset( $settings['styles'] ) ) { - $settings['styles'] = array(); - } - - // Remove existing global styles provided by core. - $styles_without_existing_global_styles = array(); - foreach ( $settings['styles'] as $style ) { - if ( - ( $is_wp_5_8 && ! gutenberg_is_global_styles_in_5_8( $style ) ) || // Can be removed when plugin minimum version is 5.9. - ( $is_wp_5_9 && ! gutenberg_is_global_styles_in_5_9( $style ) ) || // Can be removed when plugin minimum version is 6.0. - ( $is_wp_6_0 && ( ! isset( $style['isGlobalStyles'] ) || ! $style['isGlobalStyles'] ) ) - ) { - $styles_without_existing_global_styles[] = $style; - } - } - - // Recreate global styles. - $new_global_styles = array(); - $presets = array( - array( - 'css' => 'variables', - '__unstableType' => 'presets', - 'isGlobalStyles' => true, - ), - array( - 'css' => 'presets', - '__unstableType' => 'presets', - 'isGlobalStyles' => true, - ), - ); - foreach ( $presets as $preset_style ) { - $actual_css = gutenberg_get_global_stylesheet( array( $preset_style['css'] ) ); - if ( '' !== $actual_css ) { - $preset_style['css'] = $actual_css; - $new_global_styles[] = $preset_style; - } - } - - if ( WP_Theme_JSON_Resolver::theme_has_support() ) { - $block_classes = array( - 'css' => 'styles', - '__unstableType' => 'theme', - 'isGlobalStyles' => true, - ); - $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); - if ( '' !== $actual_css ) { - $block_classes['css'] = $actual_css; - $new_global_styles[] = $block_classes; - } - } - - $settings['styles'] = array_merge( $new_global_styles, $styles_without_existing_global_styles ); - } - - // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. - $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); - - if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { - $colors_by_origin = $settings['__experimentalFeatures']['color']['palette']; - $settings['colors'] = isset( $colors_by_origin['custom'] ) ? - $colors_by_origin['custom'] : ( - isset( $colors_by_origin['theme'] ) ? - $colors_by_origin['theme'] : - $colors_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) { - $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients']; - $settings['gradients'] = isset( $gradients_by_origin['custom'] ) ? - $gradients_by_origin['custom'] : ( - isset( $gradients_by_origin['theme'] ) ? - $gradients_by_origin['theme'] : - $gradients_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { - $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes']; - $settings['fontSizes'] = isset( $font_sizes_by_origin['custom'] ) ? - $font_sizes_by_origin['custom'] : ( - isset( $font_sizes_by_origin['theme'] ) ? - $font_sizes_by_origin['theme'] : - $font_sizes_by_origin['default'] - ); - } - - if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) { - $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom']; - unset( $settings['__experimentalFeatures']['color']['custom'] ); - } - if ( isset( $settings['__experimentalFeatures']['color']['customGradient'] ) ) { - $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient']; - unset( $settings['__experimentalFeatures']['color']['customGradient'] ); - } - if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { - $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize']; - unset( $settings['__experimentalFeatures']['typography']['customFontSize'] ); - } - if ( isset( $settings['__experimentalFeatures']['typography']['lineHeight'] ) ) { - $settings['enableCustomLineHeight'] = $settings['__experimentalFeatures']['typography']['lineHeight']; - unset( $settings['__experimentalFeatures']['typography']['lineHeight'] ); - } - if ( isset( $settings['__experimentalFeatures']['spacing']['units'] ) ) { - $settings['enableCustomUnits'] = $settings['__experimentalFeatures']['spacing']['units']; - unset( $settings['__experimentalFeatures']['spacing']['units'] ); - } - if ( isset( $settings['__experimentalFeatures']['spacing']['padding'] ) ) { - $settings['enableCustomSpacing'] = $settings['__experimentalFeatures']['spacing']['padding']; - unset( $settings['__experimentalFeatures']['spacing']['padding'] ); - } - - $settings['localAutosaveInterval'] = 15; - - return $settings; -} - -add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.1/block-editor-settings.php b/lib/compat/wordpress-6.1/block-editor-settings.php new file mode 100644 index 00000000000000..ed591cf60196ae --- /dev/null +++ b/lib/compat/wordpress-6.1/block-editor-settings.php @@ -0,0 +1,166 @@ +=' ) && version_compare( $wp_version, '5.9', '<' ); + $is_wp_5_9 = version_compare( $wp_version, '5.9', '>=' ) && version_compare( $wp_version, '6.0-beta1', '<' ); + $is_wp_6_0 = version_compare( $wp_version, '6.0-beta1', '>=' ); + + // Make sure the styles array exists. + // In some contexts, like the navigation editor, it doesn't. + if ( ! isset( $settings['styles'] ) ) { + $settings['styles'] = array(); + } + + // Remove existing global styles provided by core. + $styles_without_existing_global_styles = array(); + foreach ( $settings['styles'] as $style ) { + if ( + ( $is_wp_5_8 && ! gutenberg_is_global_styles_in_5_8( $style ) ) || // Can be removed when plugin minimum version is 5.9. + ( $is_wp_5_9 && ! gutenberg_is_global_styles_in_5_9( $style ) ) || // Can be removed when plugin minimum version is 6.0. + ( $is_wp_6_0 && ( ! isset( $style['isGlobalStyles'] ) || ! $style['isGlobalStyles'] ) ) + ) { + $styles_without_existing_global_styles[] = $style; + } + } + + // Recreate global styles. + $new_global_styles = array(); + $presets = array( + array( + 'css' => 'variables', + '__unstableType' => 'presets', + 'isGlobalStyles' => true, + ), + array( + 'css' => 'presets', + '__unstableType' => 'presets', + 'isGlobalStyles' => true, + ), + ); + foreach ( $presets as $preset_style ) { + $actual_css = gutenberg_get_global_stylesheet( array( $preset_style['css'] ) ); + if ( '' !== $actual_css ) { + $preset_style['css'] = $actual_css; + $new_global_styles[] = $preset_style; + } + } + + if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + $block_classes = array( + 'css' => 'styles', + '__unstableType' => 'theme', + 'isGlobalStyles' => true, + ); + $actual_css = gutenberg_get_global_stylesheet( array( $block_classes['css'] ) ); + if ( '' !== $actual_css ) { + $block_classes['css'] = $actual_css; + $new_global_styles[] = $block_classes; + } + } + + $settings['styles'] = array_merge( $new_global_styles, $styles_without_existing_global_styles ); + } + + // Copied from get_block_editor_settings() at wordpress-develop/block-editor.php. + $settings['__experimentalFeatures'] = gutenberg_get_global_settings(); + + if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { + $colors_by_origin = $settings['__experimentalFeatures']['color']['palette']; + $settings['colors'] = isset( $colors_by_origin['custom'] ) ? + $colors_by_origin['custom'] : ( + isset( $colors_by_origin['theme'] ) ? + $colors_by_origin['theme'] : + $colors_by_origin['default'] + ); + } + + if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) { + $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients']; + $settings['gradients'] = isset( $gradients_by_origin['custom'] ) ? + $gradients_by_origin['custom'] : ( + isset( $gradients_by_origin['theme'] ) ? + $gradients_by_origin['theme'] : + $gradients_by_origin['default'] + ); + } + + if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { + $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes']; + $settings['fontSizes'] = isset( $font_sizes_by_origin['custom'] ) ? + $font_sizes_by_origin['custom'] : ( + isset( $font_sizes_by_origin['theme'] ) ? + $font_sizes_by_origin['theme'] : + $font_sizes_by_origin['default'] + ); + } + + if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) { + $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom']; + unset( $settings['__experimentalFeatures']['color']['custom'] ); + } + if ( isset( $settings['__experimentalFeatures']['color']['customGradient'] ) ) { + $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient']; + unset( $settings['__experimentalFeatures']['color']['customGradient'] ); + } + if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { + $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize']; + unset( $settings['__experimentalFeatures']['typography']['customFontSize'] ); + } + if ( isset( $settings['__experimentalFeatures']['typography']['lineHeight'] ) ) { + $settings['enableCustomLineHeight'] = $settings['__experimentalFeatures']['typography']['lineHeight']; + unset( $settings['__experimentalFeatures']['typography']['lineHeight'] ); + } + if ( isset( $settings['__experimentalFeatures']['spacing']['units'] ) ) { + $settings['enableCustomUnits'] = $settings['__experimentalFeatures']['spacing']['units']; + unset( $settings['__experimentalFeatures']['spacing']['units'] ); + } + if ( isset( $settings['__experimentalFeatures']['spacing']['padding'] ) ) { + $settings['enableCustomSpacing'] = $settings['__experimentalFeatures']['spacing']['padding']; + unset( $settings['__experimentalFeatures']['spacing']['padding'] ); + } + if ( isset( $settings['__experimentalFeatures']['spacing']['customSpacingSize'] ) ) { + $settings['disableCustomSpacingSize'] = ! $settings['__experimentalFeatures']['spacing']['customSpacingSize']; + unset( $settings['__experimentalFeatures']['spacing']['customSpacingSize'] ); + } + + if ( isset( $settings['__experimentalFeatures']['spacing']['spacingSizes'] ) ) { + $spacing_sizes_by_origin = $settings['__experimentalFeatures']['spacing']['spacingSizes']; + $settings['spacingSizes'] = isset( $spacing_sizes_by_origin['custom'] ) ? + $spacing_sizes_by_origin['custom'] : ( + isset( $spacing_sizes_by_origin['theme'] ) ? + $spacing_sizes_by_origin['theme'] : + $spacing_sizes_by_origin['default'] + ); + } + + $settings['localAutosaveInterval'] = 15; + + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 946680fb44bdd7..f0b18bc2ab2d5f 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -322,4 +322,341 @@ protected function get_block_classes( $style_nodes ) { return $block_rules; } + + /** + * Presets are a set of values that serve + * to bootstrap some styles: colors, font sizes, etc. + * + * They are a unkeyed array of values such as: + * + * ```php + * array( + * array( + * 'slug' => 'unique-name-within-the-set', + * 'name' => 'Name for the UI', + * => 'value' + * ), + * ) + * ``` + * + * This contains the necessary metadata to process them: + * + * - path => Where to find the preset within the settings section. + * - prevent_override => Disables override of default presets by theme presets. + * The relationship between whether to override the defaults + * and whether the defaults are enabled is inverse: + * - If defaults are enabled => theme presets should not be overriden + * - If defaults are disabled => theme presets should be overriden + * For example, a theme sets defaultPalette to false, + * making the default palette hidden from the user. + * In that case, we want all the theme presets to be present, + * so they should override the defaults by setting this false. + * - use_default_names => whether to use the default names + * - value_key => the key that represents the value + * - value_func => optionally, instead of value_key, a function to generate + * the value that takes a preset as an argument + * (either value_key or value_func should be present) + * - css_vars => template string to use in generating the CSS Custom Property. + * Example output: "--wp--preset--duotone--blue: " will generate as many CSS Custom Properties as presets defined + * substituting the $slug for the slug's value for each preset value. + * - classes => array containing a structure with the classes to + * generate for the presets, where for each array item + * the key is the class name and the value the property name. + * The "$slug" substring will be replaced by the slug of each preset. + * For example: + * 'classes' => array( + * '.has-$slug-color' => 'color', + * '.has-$slug-background-color' => 'background-color', + * '.has-$slug-border-color' => 'border-color', + * ) + * - properties => array of CSS properties to be used by kses to + * validate the content of each preset + * by means of the remove_insecure_properties method. + */ + const PRESETS_METADATA = array( + array( + 'path' => array( 'color', 'palette' ), + 'prevent_override' => array( 'color', 'defaultPalette' ), + 'use_default_names' => false, + 'value_key' => 'color', + 'css_vars' => '--wp--preset--color--$slug', + 'classes' => array( + '.has-$slug-color' => 'color', + '.has-$slug-background-color' => 'background-color', + '.has-$slug-border-color' => 'border-color', + ), + 'properties' => array( 'color', 'background-color', 'border-color' ), + ), + array( + 'path' => array( 'color', 'gradients' ), + 'prevent_override' => array( 'color', 'defaultGradients' ), + 'use_default_names' => false, + 'value_key' => 'gradient', + 'css_vars' => '--wp--preset--gradient--$slug', + 'classes' => array( '.has-$slug-gradient-background' => 'background' ), + 'properties' => array( 'background' ), + ), + array( + 'path' => array( 'color', 'duotone' ), + 'prevent_override' => array( 'color', 'defaultDuotone' ), + 'use_default_names' => false, + 'value_func' => 'gutenberg_get_duotone_filter_property', + 'css_vars' => '--wp--preset--duotone--$slug', + 'classes' => array(), + 'properties' => array( 'filter' ), + ), + array( + 'path' => array( 'typography', 'fontSizes' ), + 'prevent_override' => false, + 'use_default_names' => true, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--font-size--$slug', + 'classes' => array( '.has-$slug-font-size' => 'font-size' ), + 'properties' => array( 'font-size' ), + ), + array( + 'path' => array( 'typography', 'fontFamilies' ), + 'prevent_override' => false, + 'use_default_names' => false, + 'value_key' => 'fontFamily', + 'css_vars' => '--wp--preset--font-family--$slug', + 'classes' => array( '.has-$slug-font-family' => 'font-family' ), + 'properties' => array( 'font-family' ), + ), + array( + 'path' => array( 'spacing', 'spacingSizes' ), + 'prevent_override' => false, + 'use_default_names' => true, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--spacing--$slug', + 'classes' => array(), + 'properties' => array( 'padding', 'margin' ), + ), + array( + 'path' => array( 'spacing', 'spacingScale' ), + 'prevent_override' => false, + 'use_default_names' => true, + 'value_key' => 'size', + 'css_vars' => '--wp--preset--spacing--$slug', + 'classes' => array(), + 'properties' => array( 'padding', 'margin' ), + ), + ); + + /** + * The valid properties under the settings key. + * + * @var array + */ + const VALID_SETTINGS = array( + 'appearanceTools' => null, + 'border' => array( + 'color' => null, + 'radius' => null, + 'style' => null, + 'width' => null, + ), + 'color' => array( + 'background' => null, + 'custom' => null, + 'customDuotone' => null, + 'customGradient' => null, + 'defaultGradients' => null, + 'defaultPalette' => null, + 'duotone' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + 'text' => null, + ), + 'custom' => null, + 'layout' => array( + 'contentSize' => null, + 'wideSize' => null, + ), + 'spacing' => array( + 'customSpacingSize' => null, + 'spacingSizes' => null, + 'spacingScale' => null, + 'blockGap' => null, + 'margin' => null, + 'padding' => null, + 'units' => null, + ), + 'typography' => array( + 'customFontSize' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ); + + /** + * Transform the spacing scale values into an array of spacing scale presets. + */ + public function get_spacing_sizes() { + $spacing_scale = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'spacingScale' ), array() ); + + if ( ! is_numeric( $spacing_scale['steps'] ) + || ! $spacing_scale['steps'] > 0 + || ! isset( $spacing_scale['mediumStep'] ) + || ! isset( $spacing_scale['units'] ) + || ! isset( $spacing_scale['operator'] ) + || ! isset( $spacing_scale['increment'] ) + || ! isset( $spacing_scale['steps'] ) + || ! is_numeric( $spacing_scale['increment'] ) + || ! is_numeric( $spacing_scale['mediumStep'] ) + || ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ) { + if ( ! empty( $spacing_scale ) ) { + trigger_error( __( 'Some of the theme.json settings.spacing.spacingScale values are invalid', 'gutenberg' ), E_USER_NOTICE ); + } + return null; + } + + $current_step = $spacing_scale['mediumStep']; + $steps_mid_point = round( ( ( $spacing_scale['steps'] ) / 2 ), 0 ); + + $x_count = null; + $below_sizes = array(); + + for ( $x = $steps_mid_point - 1; $x > 0; $x-- ) { + $current_step = '+' === $spacing_scale['operator'] + ? $current_step - $spacing_scale['increment'] + : $current_step / $spacing_scale['increment']; + + $below_sizes[] = array( + /* translators: %s: Muliple of t-shirt sizing, eg. 2X-Small */ + 'name' => $x === $steps_mid_point - 1 ? __( 'Small', 'gutenberg' ) : sprintf( __( '%sX-Small', 'gutenberg' ), strval( $x_count ) ), + 'slug' => $x * 10, + 'size' => round( $current_step, 2 ) . $spacing_scale['units'], + ); + if ( $x === $steps_mid_point - 2 ) { + $x_count = 2; + } + if ( $x < $steps_mid_point - 2 ) { + $x_count++; + } + } + + $below_sizes = array_reverse( $below_sizes ); + + $below_sizes[] = array( + 'name' => __( 'Medium', 'gutenberg' ), + 'slug' => 50, + 'size' => $spacing_scale['mediumStep'] . $spacing_scale['units'], + ); + + $current_step = $spacing_scale['mediumStep']; + $x_count = null; + $above_sizes = array(); + for ( $x = $steps_mid_point + 1; $x < $spacing_scale['steps']; $x++ ) { + $current_step = '+' === $spacing_scale['operator'] + ? $current_step + $spacing_scale['increment'] + : $current_step * $spacing_scale['increment']; + + $above_sizes[] = array( + /* translators: %s: Muliple of t-shirt sizing, eg. 2X-Large */ + 'name' => $x === $steps_mid_point + 1 ? __( 'Large', 'gutenberg' ) : sprintf( __( '%sX-Large', 'gutenberg' ), strval( $x_count ) ), + 'slug' => $x * 10, + 'size' => round( $current_step, 2 ) . $spacing_scale['units'], + ); + + if ( $x === $steps_mid_point + 2 ) { + $x_count = 2; + } + if ( $x > $steps_mid_point + 2 ) { + $x_count++; + } + } + + _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), array_merge( $below_sizes, $above_sizes ) ); + } + + /** + * Merge new incoming data. + * + * @param WP_Theme_JSON $incoming Data to merge. + */ + public function merge( $incoming ) { + $incoming_data = $incoming->get_raw_data(); + $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); + + /* + * The array_replace_recursive algorithm merges at the leaf level, + * but we don't want leaf arrays to be merged, so we overwrite it. + * + * For leaf values that are sequential arrays it will use the numeric indexes for replacement. + * We rather replace the existing with the incoming value, if it exists. + * This is the case of spacing.units. + * + * For leaf values that are associative arrays it will merge them as expected. + * This is also not the behavior we want for the current associative arrays (presets). + * We rather replace the existing with the incoming value, if it exists. + * This happens, for example, when we merge data from theme.json upon existing + * theme supports or when we merge anything coming from the same source twice. + * This is the case of color.palette, color.gradients, color.duotone, + * typography.fontSizes, or typography.fontFamilies. + * + * Additionally, for some preset types, we also want to make sure the + * values they introduce don't conflict with default values. We do so + * by checking the incoming slugs for theme presets and compare them + * with the equivalent default presets: if a slug is present as a default + * we remove it from the theme presets. + */ + $nodes = static::get_setting_nodes( $incoming_data ); + $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) ); + foreach ( $nodes as $node ) { + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); + $slugs = array_merge_recursive( $slugs_global, $slugs_node ); + + // Replace the spacing.units. + $path = array_merge( $node['path'], array( 'spacing', 'units' ) ); + $content = _wp_array_get( $incoming_data, $path, null ); + if ( isset( $content ) ) { + _wp_array_set( $this->theme_json, $path, $content ); + } + + // Replace the presets. + foreach ( static::PRESETS_METADATA as $preset ) { + $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); + + foreach ( static::VALID_ORIGINS as $origin ) { + $base_path = array_merge( $node['path'], $preset['path'] ); + $path = array_merge( $base_path, array( $origin ) ); + $content = _wp_array_get( $incoming_data, $path, null ); + if ( ! isset( $content ) ) { + continue; + } + + if ( 'theme' === $origin && $preset['use_default_names'] ) { + foreach ( $content as &$item ) { + if ( ! array_key_exists( 'name', $item ) ) { + $name = static::get_name_from_defaults( $item['slug'], $base_path ); + if ( null !== $name ) { + $item['name'] = $name; + } + } + } + } + + if ( + ( 'theme' !== $origin ) || + ( 'theme' === $origin && $override_preset ) + ) { + _wp_array_set( $this->theme_json, $path, $content ); + } else { + $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); + $content = static::filter_slugs( $content, $slugs_for_preset ); + _wp_array_set( $this->theme_json, $path, $content ); + } + } + } + } + } } diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php index 58abcc27c04ec5..efb2025d6f4ddb 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php @@ -16,6 +16,25 @@ * @access private */ class WP_Theme_JSON_Resolver_6_1 extends WP_Theme_JSON_Resolver_6_0 { + /** + * Given a theme.json structure modifies it in place + * to update certain values by its translated strings + * according to the language set by the user. + * + * @param array $theme_json The theme.json to translate. + * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. + * Default 'default'. + * @return array Returns the modified $theme_json_structure. + */ + protected static function translate( $theme_json, $domain = 'default' ) { + if ( null === static::$i18n_schema ) { + $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); + static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; + } + + return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain ); + } + /** * Return core's origin config. * diff --git a/lib/compat/wordpress-6.1/theme-i18n.json b/lib/compat/wordpress-6.1/theme-i18n.json new file mode 100644 index 00000000000000..469783409c891c --- /dev/null +++ b/lib/compat/wordpress-6.1/theme-i18n.json @@ -0,0 +1,86 @@ +{ + "title": "Style variation name", + "settings": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ], + "duotone": [ + { + "name": "Duotone name" + } + ] + }, + "spacing": { + "spacingSizes": [ + { + "name": "Space size name" + } + ] + }, + "blocks": { + "*": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ] + }, + "spacing": { + "spacingSizes": [ + { + "name": "Space size name" + } + ] + } + } + } + }, + "customTemplates": [ + { + "title": "Custom template name" + } + ], + "templateParts": [ + { + "title": "Template part name" + } + ] +} diff --git a/lib/compat/wordpress-6.1/theme.json b/lib/compat/wordpress-6.1/theme.json index 00acba508d0336..9c5bd2886b2560 100644 --- a/lib/compat/wordpress-6.1/theme.json +++ b/lib/compat/wordpress-6.1/theme.json @@ -189,7 +189,15 @@ "blockGap": null, "margin": false, "padding": false, - "units": [ "px", "em", "rem", "vh", "vw", "%" ] + "customSpacingSizes": true, + "units": [ "px", "em", "rem", "vh", "vw", "%" ], + "spacingScale": { + "operator": "*", + "increment": 1.5, + "steps": 10, + "mediumStep": 1.5, + "units": "rem" + } }, "typography": { "customFontSize": true, diff --git a/lib/experimental/class-wp-rest-block-editor-settings-controller.php b/lib/experimental/class-wp-rest-block-editor-settings-controller.php index 0d1aa47fb2b02b..d9b726f1158da2 100644 --- a/lib/experimental/class-wp-rest-block-editor-settings-controller.php +++ b/lib/experimental/class-wp-rest-block-editor-settings-controller.php @@ -275,6 +275,16 @@ public function get_item_schema() { 'type' => 'array', 'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ), ), + 'spacingSizes' => array( + 'description' => __( 'Active theme spacing sizes.', 'gutenberg' ), + 'type' => 'array', + 'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ), + ), + 'spacingScale' => array( + 'description' => __( 'Active theme spacing scale.', 'gutenberg' ), + 'type' => 'array', + 'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ), + ), ), ); diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index 6c4e5b534a4f78..ad6f4b9942dfa2 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -173,6 +173,8 @@ public static function get_merged_data( $origin = 'custom' ) { if ( 'custom' === $origin ) { $result->merge( static::get_user_data() ); } + // Generate the default spacing sizes presets. + $result->get_spacing_sizes(); return $result; } diff --git a/lib/load.php b/lib/load.php index d76ec3181b7cfe..8c77ab5ee0ba43 100644 --- a/lib/load.php +++ b/lib/load.php @@ -124,6 +124,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.1 compat. require __DIR__ . '/compat/wordpress-6.1/blocks.php'; +require __DIR__ . '/compat/wordpress-6.1/block-editor-settings.php'; require __DIR__ . '/compat/wordpress-6.1/persisted-preferences.php'; require __DIR__ . '/compat/wordpress-6.1/get-global-styles-and-settings.php'; require __DIR__ . '/compat/wordpress-6.1/class-wp-theme-json-6-1.php'; diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 75e346e0982f5b..be7ceb3433f028 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -226,6 +226,69 @@ "type": "string" }, "default": [ "px", "em", "rem", "vh", "vw", "%" ] + }, + "customSpacingSize": { + "description": "Allow users to set custom space sizes.", + "type": "boolean", + "default": true + }, + "spacingSizes": { + "description": "Space size presets for the space size selector.\nGenerates a custom property (`--wp--preset--space-size--{slug}`) per preset value.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Name of the space size preset, translatable.", + "type": "string" + }, + "slug": { + "description": "Kebab-case unique identifier for the space size preset.", + "type": "string" + }, + "size": { + "description": "CSS space-size value, including units.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "spacingScale": { + "description": "Space size presets for the space size selector.\nGenerates a custom property (`--wp--preset--space-size--{slug}`) per preset value.", + "type": "object", + "properties": { + "operator": { + "description": "With + or * depending on whether scale is generated by increment or mulitplier.", + "type": "string", + "enum": [ "+", "*" ] + }, + "increment": { + "description": "The amount to increment each step by.", + "type": "number" + }, + "steps": { + "description": "Number of steps to generate in scale.", + "type": "integer" + }, + "mediumStep": { + "description": "The value to medium setting in the scale.", + "type": "number" + }, + "units": { + "description": "Units that the scale uses, eg. rem, em, px.", + "type": "string", + "enum": [ + "px", + "em", + "rem", + "vh", + "vw", + "%" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false