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

Style Engine: add elements to the frontend #41732

Merged
merged 10 commits into from
Jul 14, 2022
82 changes: 43 additions & 39 deletions packages/block-editor/src/hooks/style.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* External dependencies
*/
import { get, has, isEmpty, kebabCase, omit } from 'lodash';
import { get, has, omit } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useContext, createPortal } from '@wordpress/element';
import { useContext, useMemo, createPortal } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
import {
getBlockSupport,
Expand All @@ -16,7 +16,10 @@ import {
__EXPERIMENTAL_ELEMENTS as ELEMENTS,
} from '@wordpress/blocks';
import { createHigherOrderComponent, useInstanceId } from '@wordpress/compose';
import { getCSSRules } from '@wordpress/style-engine';
import {
getCSSRules,
generate as generateStyles,
} from '@wordpress/style-engine';

/**
* Internal dependencies
Expand Down Expand Up @@ -105,28 +108,6 @@ export function getInlineStyles( styles = {} ) {
return output;
}

function compileElementsStyles( selector, elements = {} ) {
return Object.entries( elements )
.map( ( [ element, styles ] ) => {
const elementStyles = getInlineStyles( styles );
if ( ! isEmpty( elementStyles ) ) {
// The .editor-styles-wrapper selector is required on elements styles. As it is
// added to all other editor styles, not providing it causes reset and global
// styles to override element styles because of higher specificity.
return [
`.editor-styles-wrapper .${ selector } ${ ELEMENTS[ element ] }{`,
...Object.entries( elementStyles ).map(
( [ cssProperty, value ] ) =>
`\t${ kebabCase( cssProperty ) }: ${ value };`
),
'}',
].join( '\n' );
}
return '';
} )
.join( '\n' );
}

/**
* Filters registered block settings, extending attributes to include `style` attribute.
*
Expand Down Expand Up @@ -323,23 +304,46 @@ const withElementsStyles = createHigherOrderComponent(
'link'
);

// The Elements API only supports link colors for now,
// hence the specific omission of `link` in the elements styles.
// This might need to be refactored or removed if the Elements API
// changes or `link` supports styles beyond `color`.
const elements = skipLinkColorSerialization
? omit( props.attributes.style?.elements, [ 'link' ] )
: props.attributes.style?.elements;

const styles = compileElementsStyles(
blockElementsContainerIdentifier,
elements
);
const styles = useMemo( () => {
const rawElementsStyles = props.attributes.style?.elements;
const elementCssRules = [];
if (
rawElementsStyles &&
Object.keys( rawElementsStyles ).length > 0
) {
// Remove values based on whether serialization has been skipped for a specific style.
const filteredElementsStyles = {
...rawElementsStyles,
link: {
...rawElementsStyles.link,
color: ! skipLinkColorSerialization
? rawElementsStyles.link?.color
: undefined,
},
};

for ( const [ elementName, elementStyles ] of Object.entries(
filteredElementsStyles
) ) {
const cssRule = generateStyles( elementStyles, {
// The .editor-styles-wrapper selector is required on elements styles. As it is
// added to all other editor styles, not providing it causes reset and global
// styles to override element styles because of higher specificity.
selector: `.editor-styles-wrapper .${ blockElementsContainerIdentifier } ${ ELEMENTS[ elementName ] }`,
} );
if ( !! cssRule ) {
elementCssRules.push( cssRule );
}
}
}
return elementCssRules.length > 0 ? elementCssRules : undefined;
}, [ props.attributes.style?.elements ] );

const element = useContext( BlockList.__unstableElementContext );

return (
<>
{ elements &&
{ styles &&
element &&
createPortal(
<style
Expand All @@ -353,7 +357,7 @@ const withElementsStyles = createHigherOrderComponent(
<BlockListBlock
{ ...props }
className={
elements
props.attributes.style?.elements
? classnames(
props.className,
blockElementsContainerIdentifier
Expand Down
43 changes: 41 additions & 2 deletions packages/style-engine/src/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ describe( 'generate', () => {
expect( generate( {}, '.some-selector' ) ).toEqual( '' );
} );

it( 'should generate empty style with empty keys', () => {
expect(
generate( {
spacing: undefined,
color: undefined,
} )
).toEqual( '' );
} );

it( 'should generate inline styles where there is no selector', () => {
expect(
generate( {
Expand Down Expand Up @@ -72,14 +81,17 @@ describe( 'generate', () => {
);
} );

it( 'should parse preset values (use for elements.link.color.text)', () => {
it( 'should parse preset values', () => {
expect(
generate( {
color: {
text: 'var:preset|color|ham-sandwich',
},
spacing: { margin: '3px' },
} )
).toEqual( 'color: var(--wp--preset--color--ham-sandwich);' );
).toEqual(
'color: var(--wp--preset--color--ham-sandwich); margin: 3px;'
);
} );

it( 'should parse border rules', () => {
Expand Down Expand Up @@ -278,4 +290,31 @@ describe( 'getCSSRules', () => {
},
] );
} );

it( 'should handle styles with CSS vars', () => {
expect(
getCSSRules(
{
color: {
text: 'var:preset|color|bomba-picante',
},
spacing: { padding: '11px' },
},
{
selector: '.some-selector a',
}
)
).toEqual( [
{
selector: '.some-selector a',
key: 'color',
value: 'var(--wp--preset--color--bomba-picante)',
},
{
selector: '.some-selector a',
key: 'padding',
value: '11px',
},
] );
} );
} );