Skip to content

Commit

Permalink
Add duotone theme.json styles support
Browse files Browse the repository at this point in the history
  • Loading branch information
ajlende committed Sep 8, 2021
1 parent 4e664da commit 081c490
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 48 deletions.
103 changes: 67 additions & 36 deletions lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,31 +251,47 @@ function gutenberg_register_duotone_support( $block_type ) {
}

/**
* Render out the duotone stylesheet and SVG.
* Get the duotone stylesheet.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
* @param string $duotone_id Unique id for the duotone filter.
* @param string $duotone_selector Duotone selector as declared in block support.
*
* @return string Filtered block content.
*/
function gutenberg_render_duotone_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
function gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_selector ) {
$selectors = explode( ',', $duotone_selector );
$selectors_scoped = array_map(
function ( $selector ) use ( $duotone_id ) {
return '.' . $duotone_id . ' ' . trim( $selector );
},
$selectors
);
$selectors_group = implode( ', ', $selectors_scoped );

$duotone_support = false;
if ( $block_type && property_exists( $block_type, 'supports' ) ) {
$duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false );
}
ob_start();

$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
?>

if (
! $duotone_support ||
! $has_duotone_attribute
) {
return $block_content;
}
<style>
<?php echo $selectors_group; ?> {
filter: url( <?php echo esc_url( '#' . $duotone_id ); ?> );
}
</style>

$duotone_colors = $block['attrs']['style']['color']['duotone'];
<?php

return ob_get_clean();
}

/**
* Get the duotone SVG.
*
* @param string $duotone_id Unique id for the duotone filter.
* @param array $duotone_colors Array of CSS color strings that can be parsed by tinycolor.
*
* @return string Filtered block content.
*/
function gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors ) {
$duotone_values = array(
'r' => array(),
'g' => array(),
Expand All @@ -289,27 +305,10 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
$duotone_values['b'][] = $color['b'] / 255;
}

$duotone_id = 'wp-duotone-filter-' . uniqid();

$selectors = explode( ',', $duotone_support );
$selectors_scoped = array_map(
function ( $selector ) use ( $duotone_id ) {
return '.' . $duotone_id . ' ' . trim( $selector );
},
$selectors
);
$selectors_group = implode( ', ', $selectors_scoped );

ob_start();

?>

<style>
<?php echo $selectors_group; ?> {
filter: url( <?php echo esc_url( '#' . $duotone_id ); ?> );
}
</style>

<svg
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 0 0"
Expand Down Expand Up @@ -341,7 +340,39 @@ function ( $selector ) use ( $duotone_id ) {

<?php

$duotone = ob_get_clean();
return ob_get_clean();
}

/**
* Render out the duotone stylesheet and SVG.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function gutenberg_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 );
}

$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );

if (
! $duotone_support ||
! $has_duotone_attribute
) {
return $block_content;
}

$duotone_colors = $block['attrs']['style']['color']['duotone'];

$duotone_id = 'wp-duotone-filter-' . uniqid();

$duotone = gutenberg_get_duotone_stylesheet( $duotone_id, $duotone_support );
$duotone .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors );

// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
$content = preg_replace(
Expand Down
116 changes: 108 additions & 8 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class WP_Theme_JSON_Gutenberg {
),
'color' => array(
'background' => null,
'duotone' => null,
'gradient' => null,
'text' => null,
),
Expand Down Expand Up @@ -240,6 +241,16 @@ class WP_Theme_JSON_Gutenberg {
'text-transform' => array( 'typography', 'textTransform' ),
);

/**
* Metadata for style properties that need to use the duotone selector.
*
* Each element is a direct mapping from the CSS property name to the
* path to the value in theme.json & block attributes.
*/
const DUOTONE_PROPERTIES_METADATA = array(
'filter' => array( 'color', 'duotone' ),
);

const ELEMENTS = array(
'link' => 'a',
'h1' => 'h1',
Expand Down Expand Up @@ -357,9 +368,13 @@ private static function sanitize( $input, $valid_block_names, $valid_element_nam
* },
* 'core/heading': {
* 'selector': 'h1'
* }
* },
* 'core/group': {
* 'selector': '.wp-block-group'
* },
* 'core/cover': {
* 'selector': '.wp-block-cover',
* 'duotone': '> .wp-block-cover__image-background, > .wp-block-cover__video-background'
* }
* }
*
Expand All @@ -384,6 +399,13 @@ private static function get_blocks_metadata() {
self::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) );
}

if (
isset( $block_type->supports['color']['__experimentalDuotone'] ) &&
is_string( $block_type->supports['color']['__experimentalDuotone'] )
) {
self::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone'];
}

// Assign defaults, then overwrite those that the block sets by itself.
// If the block selector is compounded, will append the element to each
// individual block selector.
Expand Down Expand Up @@ -537,16 +559,17 @@ private static function get_property_value( $styles, $path ) {
* ```
*
* @param array $styles Styles to process.
* @param array $properties Properties metadata.
*
* @return array Returns the modified $declarations.
*/
private static function compute_style_properties( $styles ) {
private static function compute_style_properties( $styles, $properties = self::PROPERTIES_METADATA ) {
$declarations = array();
if ( empty( $styles ) ) {
return $declarations;
}

foreach ( self::PROPERTIES_METADATA as $css_property => $value_path ) {
foreach ( $properties as $css_property => $value_path ) {
$value = self::get_property_value( $styles, $value_path );

// Skip if empty and not "0" or value represents array of longhand values.
Expand Down Expand Up @@ -586,6 +609,36 @@ private static function append_to_selector( $selector, $to_append ) {
return implode( ',', $new_selectors );
}

/**
* Function that scopes a selector with another one. This works a bit like
* SCSS nesting except the `&` operator isn't supported.
*
* <code>
* $scope = '.a, .b .c';
* $selector = '> .x, .y';
* $merged = scope_selector( $scope, $selector );
* // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y'
* </code>
*
* @param string $scope Selector to scope to.
* @param string $selector Original selector.
*
* @return string Scoped selector.
*/
private static function scope_selector( $scope, $selector ) {
$scopes = explode( ',', $scope );
$selectors = explode( ',', $selector );

$selectors_scoped = array();
foreach ( $scopes as $outer ) {
foreach ( $selectors as $inner ) {
$selectors_scoped[] = trim( $outer ) . ' ' . trim( $inner );
}
}

return implode( ', ', $selectors_scoped );
}

/**
* Function that given an array of presets keyed by origin
* and the value key of the preset returns an array where each key is
Expand Down Expand Up @@ -812,19 +865,49 @@ private function get_block_classes( $style_nodes ) {
continue;
}

$selector = $metadata['selector'];

if ( self::ROOT_BLOCK_SELECTOR === $selector ) {
$block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }';
}

$node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
$selector = $metadata['selector'];
$declarations = self::compute_style_properties( $node );
$block_rules .= self::to_ruleset( $selector, $declarations );

if ( self::ROOT_BLOCK_SELECTOR === $selector ) {
$block_rules .= '.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }';
if ( isset( $metadata['duotone'] ) ) {
$selector = self::scope_selector( $selector, $metadata['duotone'] );
$declarations = self::compute_style_properties( $node, self::DUOTONE_PROPERTIES_METADATA );
$block_rules .= self::to_ruleset( $selector, $declarations );
}
}

return $block_rules;
}

/**
* Gets the SVGs for duotone filter support.
*
* @param array $settings Settings per block.
*
* @return string The SVGs containing the duotone filters.
*/
private function get_svg_filters( $settings ) {
if ( ! isset( $settings['color']['duotone'] ) ) {
return;
}

$block_svgs = '';

foreach ( $settings['color']['duotone'] as $swatch ) {
$duotone_id = 'wp-duotone-filter-' . $swatch['slug'];
$duotone_colors = $swatch['colors'];
$block_svgs .= gutenberg_get_duotone_svg_filter( $duotone_id, $duotone_colors );
}

return $block_svgs;
}

/**
* Creates new rulesets as classes for each preset value such as:
*
Expand Down Expand Up @@ -945,11 +1028,13 @@ public function get_template_parts() {
* [
* [
* 'path' => [ 'path', 'to', 'some', 'node' ],
* 'selector' => 'CSS selector for some node'
* 'selector' => 'CSS selector for some node',
* 'duotone' => 'CSS selector for duotone for some node'
* ],
* [
* 'path' => ['path', 'to', 'other', 'node' ],
* 'selector' => 'CSS selector for other node'
* 'selector' => 'CSS selector for other node',
* 'duotone' => null
* ],
* ]
*
Expand Down Expand Up @@ -990,9 +1075,15 @@ private static function get_style_nodes( $theme_json, $selectors = array() ) {
$selector = $selectors[ $name ]['selector'];
}

$duotone_selector = null;
if ( isset( $selectors[ $name ]['duotone'] ) ) {
$duotone_selector = $selectors[ $name ]['duotone'];
}

$nodes[] = array(
'path' => array( 'styles', 'blocks', $name ),
'selector' => $selector,
'duotone' => $duotone_selector,
);

if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
Expand Down Expand Up @@ -1087,6 +1178,15 @@ public function get_stylesheet( $type = 'all' ) {
}
}

/**
* Returns the SVGs for filters used by the stylesheets.
*
* @return string SVGs.
*/
public function get_svgs() {
return $this->get_svg_filters( $this->get_settings() );
}

/**
* Merge new incoming data.
*
Expand Down
22 changes: 22 additions & 0 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ function gutenberg_experimental_global_styles_enqueue_assets() {
}
}

/**
* Fetches the preferences for each origin (core, theme, user)
* and enqueues the resulting SVGs for media filter support.
*
* TODO: Is this the right way to render the SVGs? Maybe I should render them as
* CSS variables so they can be included in the stylesheet?
*/
function gutenberg_experimental_global_styles_enqueue_svg_filters() {
if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) {
return;
}

$settings = gutenberg_get_default_block_editor_settings();
$all = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings );

// TODO: Should this be cached like the stylesheet?
echo $all->get_svgs();
}

/**
* Adds the necessary settings for the Global Styles client UI.
*
Expand Down Expand Up @@ -336,6 +355,9 @@ function gutenberg_global_styles_include_support_for_wp_variables( $allow_css, $
add_action( 'init', 'gutenberg_experimental_global_styles_register_user_cpt' );
add_action( 'wp_enqueue_scripts', 'gutenberg_experimental_global_styles_enqueue_assets' );

// SVG defs need to be within a container that is displayed (not inside a display: none or in the header).
add_action( is_admin() ? 'admin_footer' : 'wp_footer', 'gutenberg_experimental_global_styles_enqueue_svg_filters' );

// kses actions&filters.
add_action( 'init', 'gutenberg_global_styles_kses_init' );
add_action( 'set_current_user', 'gutenberg_global_styles_kses_init' );
Expand Down

0 comments on commit 081c490

Please sign in to comment.