Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add duotone theme.json styles support #34667

Merged
merged 24 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bbdd7ab
Add duotone theme.json styles support
ajlende Sep 8, 2021
5ec2efe
Add support for generating duotone CSS variables
ajlende Sep 8, 2021
3c0a97e
Add value_func and value_args metadata to compute duotone
ajlende Sep 9, 2021
ec86c0b
Update phpunit tests
ajlende Sep 9, 2021
358c456
Fix duotone filter id on css variables
ajlende Sep 9, 2021
8c421cc
Exmplain why !important is needed
ajlende Sep 9, 2021
e9abc0b
Fix get_settings_values_by_slug example
ajlende Sep 9, 2021
cebab75
Add color.duotone to PATHS_WITH_MERGE
ajlende Sep 9, 2021
5956f18
Rename $meta to $preset_meta to be more descriptive
ajlende Sep 9, 2021
c15c1cf
Render duotone styles when settings are disabled
ajlende Sep 14, 2021
3c2c166
Improve mechanism to declare a selector for the property
oandregal Sep 21, 2021
220d86d
Revert "Improve mechanism to declare a selector for the property"
oandregal Sep 22, 2021
a0134b1
Merge branch 'trunk' into add/duotone-theme-json-styles
ajlende Sep 22, 2021
537d608
duotone-filter to just duotone css var infix
ajlende Sep 23, 2021
23cd929
Merge branch 'trunk' into add/duotone-theme-json-styles
ajlende Sep 27, 2021
775717e
Revert duotone SVG to be inside the footer
ajlende Sep 27, 2021
2a6a7e8
Simplify value_func args
ajlende Sep 27, 2021
c147b04
Additionally strip multiple spaces in SVG
ajlende Sep 27, 2021
e31fa8e
Improve code quality and inline docs
ajlende Sep 27, 2021
8aaa12f
Fix value_func after rename
ajlende Sep 27, 2021
c793a69
Improve inline docs
ajlende Sep 27, 2021
df50ef0
Update duotone-filter -> duotone for generated ids
ajlende Sep 28, 2021
53b154b
Rename preset_meta -> preset_metadata
ajlende Sep 28, 2021
008c5d5
Move duotone styles from color -> filter
ajlende Sep 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 89 additions & 61 deletions lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,31 +251,17 @@ function gutenberg_register_duotone_support( $block_type ) {
}

/**
* Render out the duotone stylesheet and SVG.
* Renders the duotone filter SVG and returns the CSS filter property to
* reference the rendered SVG.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
* @param array $preset Duotone preset value as seen in theme.json.
*
* @return string Duotone CSS filter property.
*/
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'];

function gutenberg_render_duotone_filter_preset( $preset ) {
$duotone_id = $preset['slug'];
$duotone_colors = $preset['colors'];
$filter_id = 'wp-duotone-' . $duotone_id;
$duotone_values = array(
'r' => array(),
'g' => array(),
Expand All @@ -289,29 +275,12 @@ 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"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 0 0"
width="0"
height="0"
Expand All @@ -320,15 +289,15 @@ function ( $selector ) use ( $duotone_id ) {
style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
>
<defs>
<filter id="<?php echo esc_attr( $duotone_id ); ?>">
<filter id="<?php echo esc_attr( $filter_id ); ?>">
<feColorMatrix
type="matrix"
<?php // phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent ?>
values=".299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
0 0 0 1 0"
<?php // phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent ?>
values="
.299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
0 0 0 1 0
"
/>
<feComponentTransfer color-interpolation-filters="sRGB" >
<feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
Expand All @@ -341,25 +310,84 @@ function ( $selector ) use ( $duotone_id ) {

<?php

$duotone = ob_get_clean();
$svg = ob_get_clean();

// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
$content = preg_replace(
'/' . preg_quote( 'class="', '/' ) . '/',
'class="' . $duotone_id . ' ',
$block_content,
1
);
if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) {
// Clean up the whitespace.
$svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg );
$svg = preg_replace( '/> </', '><', $svg );
$svg = trim( $svg );
}

add_action(
// Ideally we should use wp_head, but SVG defs can't be put in there.
'wp_footer',
function () use ( $duotone ) {
echo $duotone;
// Safari doesn't render SVG filters defined in data URIs,
// and SVG filters won't render in the head of a document,
// so the next best place to put the SVG is in the footer.
is_admin() ? 'admin_footer' : 'wp_footer',
function () use ( $svg ) {
echo $svg;
}
);

return $content;
return "url('#" . $filter_id . "')";
}

/**
* 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;
}

$filter_preset = array(
'slug' => uniqid(),
'colors' => $block['attrs']['style']['color']['duotone'],
);
$filter_property = gutenberg_render_duotone_filter_preset( $filter_preset );
$filter_id = 'wp-duotone-' . $filter_preset['slug'];

$scope = '.' . $filter_id;
$selectors = explode( ',', $duotone_support );
$scoped = array();
foreach ( $selectors as $sel ) {
$scoped[] = $scope . ' ' . trim( $sel );
}
$selector = implode( ', ', $scoped );

// !important is needed because these styles render before global styles,
// and they should be overriding the duotone filters set by global styles.
$filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG
? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"
: $selector . '{filter:' . $filter_property . ' !important;}';
Comment on lines +374 to +378
Copy link
Contributor Author

@ajlende ajlende Sep 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be safe to add specificity by prepending this with .wp-site-blocks instead of using !important? Can I count on having that as a container? Is it weird to use body instead of that class if it's not available?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can rely on .wp-site-blocks, but i do wonder if this is a valid use of !important - since this is a setting on an individual block, I think it should always take precedence over other CSS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was mention of a similar situation for elements block supports in the #core-editor Slack today.

The linked issue is #33437 and there's a fix by removing !important in #34689. That issue is caused by the cascade and because sometimes buttons, that we want to style differently, use the <a> tag.

The filter property doesn't cascade the same way, so we're probably fine here. But the elements block supports goes to show that just because we're setting an individual block, !important still might not be safe to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific need for the duotone filters to be enqueued before the global styles?

Copy link
Member

@oandregal oandregal Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if we can make it work without the !important is nice.

The issue with #33437 was that .wp-element-* a targeted any a element in a container (group with paragraph with link + button + social link blocks). So the issue was about how unspecific the selector was. Which makes me think: is it possible that the container with the .wp-duotone-ID has an image with duotone and another without it? I'm just not familiar with all the blocks this can affect to. I can't think of any off the top of my head.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More context about where we use !important: we use it in the preset classes, such as .has-{value}-color to make sure the user selection is respected despite its low specificity. More context #29533

In the case of the link color, it was probably too much due to the wide reach of the selector and side-effects. We're reverting it in this case if the current PR seems solid.


wp_register_style( $filter_id, false, array(), true, true );
wp_add_inline_style( $filter_id, $filter_style );
wp_enqueue_style( $filter_id );

// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
return preg_replace(
'/' . preg_quote( 'class="', '/' ) . '/',
'class="' . $filter_id . ' ',
$block_content,
1
);
}

// Register the block support.
Expand Down
Loading