Skip to content

Commit

Permalink
Add defaultSpacingSizes option (theme.json v3) (WordPress#61842)
Browse files Browse the repository at this point in the history
Co-authored-by: ajlende <[email protected]>
Co-authored-by: MaggieCabrera <[email protected]>
Co-authored-by: ellatrix <[email protected]>
Co-authored-by: bgardner <[email protected]>
Co-authored-by: carolinan <[email protected]>
Co-authored-by: dabowman <[email protected]>
Co-authored-by: hanneslsm <[email protected]>
  • Loading branch information
8 people authored and carstingaxion committed Jun 4, 2024
1 parent 4321025 commit 7b79939
Show file tree
Hide file tree
Showing 20 changed files with 400 additions and 118 deletions.
5 changes: 5 additions & 0 deletions backport-changelog/6.6/6616.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
https://github.com/WordPress/wordpress-develop/pull/6616

* https://github.com/WordPress/gutenberg/pull/58409
* https://github.com/WordPress/gutenberg/pull/61328
* https://github.com/WordPress/gutenberg/pull/61842
11 changes: 1 addition & 10 deletions docs/how-to-guides/themes/global-settings-and-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,16 +336,7 @@ The following presets can be defined via `theme.json`:
- `color.palette`:
- generates 3 classes per preset value: color, background-color, and border-color.
- generates a single custom property per preset value.
- `spacing.spacingScale`: used to generate an array of spacing preset sizes for use with padding, margin, and gap settings.
- `operator`: specifies how to calculate the steps with either `*` for multiplier, or `+` for sum.
- `increment`: the amount to increment each step by. Core by default uses a 'perfect 5th' multiplier of `1.5`.
- `steps`: the number of steps to generate in the spacing scale. The default is 7. To prevent the generation of the spacing presets, and to disable the related UI, this can be set to `0`.
- `mediumStep`: the steps in the scale are generated descending and ascending from a medium step, so this should be the size value of the medium space, without the unit. The default medium step is `1.5rem` so the mediumStep value is `1.5`.
- `unit`: the unit the scale uses, eg. `px, rem, em, %`. The default is `rem`.
- `spacing.spacingSizes`: themes can choose to include a static `spacing.spacingSizes` array of spacing preset sizes if they have a sequence of sizes that can't be generated via an increment or multiplier.
- `name`: a human readable name for the size, eg. `Small, Medium, Large`.
- `slug`: the machine readable name. In order to provide the best cross site/theme compatibility the slugs should be in the format, "10","20","30","40","50","60", with "50" representing the `Medium` size value.
- `size`: the size, including the unit, eg. `1.5rem`. It is possible to include fluid values like `clamp(2rem, 10vw, 20rem)`.
- `spacing.spacingSizes`/`spacing.spacingScale`: generates a single custom property per preset value.
- `typography.fontSizes`: generates a single class and custom property per preset value.
- `typography.fontFamilies`: generates a single custom property per preset value.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Settings related to spacing.
| padding | boolean | false | |
| units | array | px,em,rem,vh,vw,% | |
| customSpacingSize | boolean | true | |
| defaultSpacingSizes | boolean | true | |
| spacingSizes | array | | name, size, slug |
| spacingScale | object | | |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,27 @@ The new `defaultFontSizes` option gives control over showing default font sizes

It is `true` by default when switching to v3. This is to be consistent with how other `default*` options work such as `settings.color.defaultPalette`, but differs from the behavior in v2.

In theme.json v2, the default font sizes were only shown when theme sizes were not defined. A theme providing font sizes with the same slugs as the defaults would always override the default ones.

To keep behavior similar to v2 with a v3 theme.json:
* If you do not have any `fontSizes` defined, `defaultFontSizes` can be left out or set to `true`.
* If you have some `fontSizes` defined, set `defaultFontSizes` to `false`.

#### `settings.spacing.defaultSpacingSizes`

In theme.json v2, there are two settings that could be used to set theme level spacing sizes: `settings.spacing.spacingSizes` and `settings.spacing.spacingScale`. Setting both `spacingSizes` _and_ `spacingScale` would only use the values from `spacingSizes`. And setting either of them would always replace the entire set of default spacing sizes provided by WordPress.

The default `spacingSizes` slugs provided by WordPress are: `20`, `30`, `40`, `50`, `60`, `70`, and `80`.

The new `defaultSpacingSizes` option gives control over showing default spacing sizes and preventing those defaults from being overridden.

- When set to `true` it will show the default spacing sizes and prevent them from being overridden by the theme.
- When set to `false` it will hide the default spacing sizes and allow the theme to use the default slugs.

`defaultSpacingSizes` is `true` by default when switching to v3. This is to be consistent with how other `default*` options work such as `settings.color.defaultPalette`, but differs from the behavior in v2.

Additionally, in v3 both `spacingSizes` and `spacingScale` can be set at the same time. Presets defined in `spacingSizes` with slugs matching the generated presets from `spacingSizes` will override the generated ones.

To keep behavior similar to v2 with a v3 theme.json:
* If you do not have any `spacingSizes` presets or `spacingScale` config defined, `defaultSpacingSizes` can be left out or set to `true`.
* If you disabled default spacing sizes by setting `spacingScale` to `{ "steps": 0 }`, remove the `spacingScale` config and set `defaultSpacingSizes` to `false`.
* If you defined only one of either `spacingScale` or `spacingSizes` for your presets, set `defaultSpacingSizes` to `false`.
* If you defined both `spacingScale` and `spacingSizes`, remove the `spacingSizes` config _and_ set `defaultSpacingSizes` to `false`.
187 changes: 168 additions & 19 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.0.0 Replaced `override` with `prevent_override` and updated the
* `prevent_override` value for `color.duotone` to use `color.defaultDuotone`.
* @since 6.2.0 Added 'shadow' presets.
* @since 6.6.0 Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' and spacing size presets to use `spacing.defaultSpacingSizes`.
* @since 6.6.0 Added `aspectRatios`.
* @var array
*/
Expand Down Expand Up @@ -187,7 +188,7 @@ class WP_Theme_JSON_Gutenberg {
),
array(
'path' => array( 'spacing', 'spacingSizes' ),
'prevent_override' => false,
'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ),
'use_default_names' => true,
'value_key' => 'size',
'css_vars' => '--wp--preset--spacing--$slug',
Expand Down Expand Up @@ -427,13 +428,14 @@ class WP_Theme_JSON_Gutenberg {
'sticky' => null,
),
'spacing' => array(
'customSpacingSize' => null,
'spacingSizes' => null,
'spacingScale' => null,
'blockGap' => null,
'margin' => null,
'padding' => null,
'units' => null,
'customSpacingSize' => null,
'defaultSpacingSizes' => null,
'spacingSizes' => null,
'spacingScale' => null,
'blockGap' => null,
'margin' => null,
'padding' => null,
'units' => null,
),
'shadow' => array(
'presets' => null,
Expand Down Expand Up @@ -727,6 +729,8 @@ public static function get_element_class_name( $element ) {
* Constructor.
*
* @since 5.8.0
* @since 6.6.0 Key spacingScale by origin, and pre-generate the
* spacingSizes from spacingScale.
*
* @param array $theme_json A structure that follows the theme.json schema.
* @param string $origin Optional. What source of data this object represents.
Expand All @@ -742,8 +746,8 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gut
$valid_block_names = array_keys( $registry->get_all_registered() );
$valid_element_names = array_keys( static::ELEMENTS );
$valid_variations = static::get_valid_block_style_variations();
$theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
$this->theme_json = static::maybe_opt_in_into_settings( $theme_json );
$this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
$this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json );

// Internally, presets are keyed by origin.
$nodes = static::get_setting_nodes( $this->theme_json );
Expand All @@ -762,6 +766,27 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gut
}
}
}

// In addition to presets, spacingScale (which generates presets) is also keyed by origin.
$scale_path = array( 'settings', 'spacing', 'spacingScale' );
$spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null );
if ( null !== $spacing_scale ) {
// If the spacingScale is not already keyed by origin.
if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) {
_wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) );
}
}

// Pre-generate the spacingSizes from spacingScale.
$scale_path = array( 'settings', 'spacing', 'spacingScale', $origin );
$spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null );
if ( isset( $spacing_scale ) ) {
$sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin );
$spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() );
$spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale );
$merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes );
_wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes );
}
}

/**
Expand Down Expand Up @@ -2947,13 +2972,49 @@ protected static function get_metadata_boolean( $data, $path, $default_value = f
*
* @since 5.8.0
* @since 5.9.0 Duotone preset also has origins.
* @since 6.6.0 Use the spacingScale keyed by origin, and re-generate the
* spacingSizes from spacingScale.
*
* @param WP_Theme_JSON_Gutenberg $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 );

/*
* Recompute all the spacing sizes based on the new hierarchy of data. In the constructor
* spacingScale and spacingSizes are both keyed by origin and VALID_ORIGINS is ordered, so
* we can allow partial spacingScale data to inherit missing data from earlier layers when
* computing the spacing sizes.
*
* This happens before the presets are merged to ensure that default spacing sizes can be
* removed from the theme origin if $prevent_override is true.
*/
$flattened_spacing_scale = array();
foreach ( static::VALID_ORIGINS as $origin ) {
$scale_path = array( 'settings', 'spacing', 'spacingScale', $origin );

// Apply the base spacing scale to the current layer.
$base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() );
$flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale );

$spacing_scale = _wp_array_get( $incoming_data, $scale_path, null );
if ( ! isset( $spacing_scale ) ) {
continue;
}

// Allow partial scale settings by merging with lower layers.
$flattened_spacing_scale = array_replace( $flattened_spacing_scale, $spacing_scale );

// Generate and merge the scales for this layer.
$sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin );
$spacing_sizes = _wp_array_get( $incoming_data, $sizes_path, array() );
$spacing_scale_sizes = static::compute_spacing_sizes( $flattened_spacing_scale );
$merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes );

_wp_array_set( $incoming_data, $sizes_path, $merged_spacing_sizes );
}

/*
* The array_replace_recursive algorithm merges at the leaf level,
* but we don't want leaf arrays to be merged, so we overwrite it.
Expand Down Expand Up @@ -3733,12 +3794,19 @@ public function get_data() {
/**
* Sets the spacingSizes array based on the spacingScale values from theme.json.
*
* No longer used since theme.json version 3 as the spacingSizes are now
* automatically generated during construction and merge instead of manually
* set in the resolver.
*
* @since 6.1.0
* @deprecated 6.6.0
*
* @return null|void
*/
public function set_spacing_sizes() {
$spacing_scale = $this->theme_json['settings']['spacing']['spacingScale'] ?? array();
_deprecated_function( __METHOD__, '6.6.0' );

$spacing_scale = $this->theme_json['settings']['spacing']['spacingScale']['default'] ?? array();

// Gutenberg didn't have the 1st isset check.
if ( ! isset( $spacing_scale['steps'] )
Expand All @@ -3762,6 +3830,94 @@ public function set_spacing_sizes() {
return null;
}

$spacing_sizes = static::compute_spacing_sizes( $spacing_scale );

// If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes.
if ( $spacing_scale['steps'] <= 7 ) {
for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) {
$spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 );
}
}

_wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes );
}

/**
* Merges two sets of spacing size presets.
*
* @since 6.6.0
*
* @param array $base The base set of spacing sizes.
* @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values.
* @return array The merged set of spacing sizes.
*/
private static function merge_spacing_sizes( $base, $incoming ) {
$merged = array();
foreach ( $base as $item ) {
$merged[ $item['slug'] ] = $item;
}
foreach ( $incoming as $item ) {
$merged[ $item['slug'] ] = $item;
}
return array_values( $merged );
}

/**
* Generates a set of spacing sizes by starting with a medium size and
* applying an operator with an increment value to generate the rest of the
* sizes outward from the medium size. The medium slug is '50' with the rest
* of the slugs being 10 apart. The generated names use t-shirt sizing.
*
* Example:
*
* $spacing_scale = array(
* 'steps' => 4,
* 'mediumStep' => 16,
* 'unit' => 'px',
* 'operator' => '+',
* 'increment' => 2,
* );
* $spacing_sizes = static::compute_spacing_sizes( $spacing_scale );
* // -> array(
* // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ),
* // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ),
* // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ),
* // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ),
* // )
*
* @since 6.6.0
*
* @param array $spacing_scale {
* The spacing scale values. All are required.
*
* @type int $steps The number of steps in the scale. (up to 10 steps are supported.)
* @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.)
* @type string $unit The CSS unit to use for the sizes.
* @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'.
* @type float $increment The value used with the operator to generate the other sizes.
* }
* @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid.
*/
private static function compute_spacing_sizes( $spacing_scale ) {
/*
* This condition is intentionally missing some checks on ranges for the values in order to
* keep backwards compatibility with the previous implementation.
*/
if (
! isset( $spacing_scale['steps'] ) ||
! is_numeric( $spacing_scale['steps'] ) ||
0 === $spacing_scale['steps'] ||
! isset( $spacing_scale['mediumStep'] ) ||
! is_numeric( $spacing_scale['mediumStep'] ) ||
! isset( $spacing_scale['unit'] ) ||
! isset( $spacing_scale['operator'] ) ||
( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ||
! isset( $spacing_scale['increment'] ) ||
! is_numeric( $spacing_scale['increment'] )
) {
return array();
}

$unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] );
$current_step = $spacing_scale['mediumStep'];
$steps_mid_point = round( $spacing_scale['steps'] / 2, 0 );
Expand Down Expand Up @@ -3844,14 +4000,7 @@ public function set_spacing_sizes() {
$spacing_sizes[] = $above_sizes_item;
}

// If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes.
if ( $spacing_scale['steps'] <= 7 ) {
for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) {
$spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 );
}
}

_wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes );
return $spacing_sizes;
}

/**
Expand Down
3 changes: 0 additions & 3 deletions lib/class-wp-theme-json-resolver-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,6 @@ public static function get_merged_data( $origin = 'custom' ) {
$result = new WP_Theme_JSON_Gutenberg();
$result->merge( static::get_core_data() );
if ( 'default' === $origin ) {
$result->set_spacing_sizes();
return $result;
}

Expand All @@ -611,12 +610,10 @@ public static function get_merged_data( $origin = 'custom' ) {

$result->merge( static::get_theme_data() );
if ( 'theme' === $origin ) {
$result->set_spacing_sizes();
return $result;
}

$result->merge( static::get_user_data() );
$result->set_spacing_sizes();
return $result;
}

Expand Down
Loading

0 comments on commit 7b79939

Please sign in to comment.