-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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 interactivity states UI support via Styles Engine #41708
Changes from all commits
11d647f
3350278
61f00f6
e33be11
4221e86
cbedaba
6838a16
38d3bb2
5783d9f
b0d7958
91bb650
879d300
d5f261e
3e02bea
6248a02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,26 +96,39 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { | |
* should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties | ||
* and work for any element and style. | ||
*/ | ||
$skip_link_color_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); | ||
// $skip_link_color_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); | ||
|
||
if ( $skip_link_color_serialization ) { | ||
return null; | ||
// if ( $skip_link_color_serialization ) { | ||
// return null; | ||
// } | ||
$class_name = gutenberg_get_elements_class_name( $block ); | ||
// $link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; | ||
|
||
$css_styles = ''; | ||
|
||
// $style_definition = _wp_array_get( WP_Style_Engine::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null ); | ||
|
||
if ( ! is_array( $element_block_styles ) ) { | ||
return; | ||
} | ||
$class_name = gutenberg_get_elements_class_name( $block ); | ||
$link_block_styles = isset( $element_block_styles['link'] ) ? $element_block_styles['link'] : null; | ||
|
||
if ( $link_block_styles ) { | ||
$styles = gutenberg_style_engine_generate( | ||
$link_block_styles, | ||
array( | ||
'selector' => ".$class_name a", | ||
'css_vars' => true, | ||
) | ||
); | ||
|
||
if ( ! empty( $styles['css'] ) ) { | ||
gutenberg_enqueue_block_support_styles( $styles['css'] ); | ||
} | ||
// Currently this is `elements -> link -> DEFS`. | ||
// In the future we should extend $element_block_styles | ||
// to include all DEFS from all supported elements. | ||
$block_styles = array( | ||
'elements' => $element_block_styles, | ||
); | ||
|
||
$style_defs = gutenberg_style_engine_generate( | ||
$block_styles, | ||
array( | ||
'selector' => ".$class_name", | ||
'css_vars' => true, | ||
) | ||
); | ||
|
||
if ( ! empty( $style_defs['css'] ) ) { | ||
gutenberg_enqueue_block_support_styles( $style_defs['css'] ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This enqueues the |
||
} | ||
|
||
return null; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,6 +110,13 @@ const resetAllLinkFilter = ( attributes ) => ( { | |
), | ||
} ); | ||
|
||
const resetAllLinkHoverFilter = ( attributes ) => ( { | ||
style: clearColorFromStyles( | ||
[ 'elements', 'link', 'states', 'hover', 'color', 'text' ], | ||
attributes.style | ||
), | ||
} ); | ||
|
||
/** | ||
* Clears all background color related properties including gradients from | ||
* supplied block attributes. | ||
|
@@ -216,7 +223,6 @@ export function addSaveProps( props, blockType, attributes ) { | |
backgroundColor || | ||
style?.color?.background || | ||
( hasGradient && ( gradient || style?.color?.gradient ) ); | ||
|
||
const newClassName = classnames( | ||
props.className, | ||
textClass, | ||
|
@@ -232,6 +238,9 @@ export function addSaveProps( props, blockType, attributes ) { | |
'has-background': serializeHasBackground && hasBackground, | ||
'has-link-color': | ||
shouldSerialize( 'link' ) && style?.elements?.link?.color, | ||
'has-link-hover-color': | ||
shouldSerialize( 'link' ) && | ||
style?.elements?.link?.states?.hover?.color, | ||
} | ||
); | ||
props.className = newClassName ? newClassName : undefined; | ||
|
@@ -284,6 +293,7 @@ const getLinkColorFromAttributeValue = ( colors, value ) => { | |
*/ | ||
export function ColorEdit( props ) { | ||
const { name: blockName, attributes } = props; | ||
|
||
// Some color settings have a special handling for deprecated flags in `useSetting`, | ||
// so we can't unwrap them by doing const { ... } = useSetting('color') | ||
// until https://github.com/WordPress/gutenberg/issues/37094 is fixed. | ||
|
@@ -443,6 +453,26 @@ export function ColorEdit( props ) { | |
}; | ||
}; | ||
|
||
const onChangeLinkHoverColor = ( value ) => { | ||
const colorObject = getColorObjectByColorValue( allSolids, value ); | ||
const newLinkColorValue = colorObject?.slug | ||
? `var:preset|color|${ colorObject.slug }` | ||
: value; | ||
|
||
const newStyle = cleanEmptyObject( | ||
immutableSet( | ||
localAttributes.current?.style, | ||
[ 'elements', 'link', 'states', 'hover', 'color', 'text' ], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This structure is intended to marry up the the nesting within the It's all very hard coded at this point. |
||
newLinkColorValue | ||
) | ||
); | ||
props.setAttributes( { style: newStyle } ); | ||
localAttributes.current = { | ||
...localAttributes.current, | ||
...{ style: newStyle }, | ||
}; | ||
}; | ||
|
||
const enableContrastChecking = | ||
Platform.OS === 'web' && ! gradient && ! style?.color?.gradient; | ||
|
||
|
@@ -508,6 +538,20 @@ export function ColorEdit( props ) { | |
isShownByDefault: defaultColorControls?.link, | ||
resetAllFilter: resetAllLinkFilter, | ||
}, | ||
{ | ||
label: __( 'Link Hover' ), | ||
onColorChange: onChangeLinkHoverColor, | ||
colorValue: getLinkColorFromAttributeValue( | ||
allSolids, | ||
style?.elements?.link?.states?.hover?.color | ||
?.text | ||
), | ||
clearable: !! style?.elements?.link?.states | ||
?.hover?.color?.text, | ||
isShownByDefault: | ||
defaultColorControls?.link?.states?.hover, | ||
resetAllFilter: resetAllLinkHoverFilter, | ||
}, | ||
] | ||
: [] ), | ||
] } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { get, has, isEmpty, kebabCase, omit } from 'lodash'; | ||
import { get, has, isEmpty, kebabCase, omit, set } from 'lodash'; | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
|
@@ -60,8 +60,9 @@ function compileStyleValue( uncompiledValue ) { | |
/** | ||
* Returns the inline styles to add depending on the style object | ||
* | ||
* @param {Object} styles Styles configuration. | ||
* @param {Object} styles Styles configuration. | ||
* | ||
* @param options | ||
* @return {Object} Flattened CSS variables declaration. | ||
*/ | ||
export function getInlineStyles( styles = {} ) { | ||
|
@@ -98,28 +99,67 @@ export function getInlineStyles( styles = {} ) { | |
// The goal is to move everything to server side generated engine styles | ||
// This is temporary as we absorb more and more styles into the engine. | ||
const extraRules = getCSSRules( styles ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
extraRules.forEach( ( rule ) => { | ||
output[ rule.key ] = rule.value; | ||
let pseudoSelector = ''; | ||
// Key value data structure cannot represent pseudo selectors. | ||
// Create a nested "states" key for pseudo selector rules. | ||
if ( rule?.selector?.startsWith( ':' ) ) { | ||
// Primitive check for pseudo selector. In future | ||
// we should make this a formal prop of the style rule from getCSSRules. | ||
pseudoSelector = rule.selector.replace( ':', '' ); | ||
set( output, [ 'states', pseudoSelector, rule.key ], rule.value ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing this changes the contract implied by Not sure how we avoid this. We need to convey the information about pseudo selectors but the data structure isn't conducive to this. Ideas welcomed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ramonjd The key / value data structure here isn't conducive to handling pseudo selectors, but we need it to in order to output the correct rules within the editor. What do you think of my approach? Are we abusing things too heavily here? Or should we change the returned data structure and update all instances within Gutenberg? This function isn't a public API so we should be ok in that regard. Your expertise would be greatly appreciated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @getdave Thanks for getting things going here. It's a tough one. I remember hooks/style.js threw some chilli into the soup when I first look at porting elements over to the JS style engine.
Totally. I can't help but recommend a more iterative approach here, that is, first let's get basic support for elements in the style engine in, then deal with new functionality. Similar to what we've been looking at over in #41619 I'm not saying it's the panacea, but it has helped to smooth out progress, and gives us time to consider holistic approaches. Along those lines I've started on the JS part for elements over here: #41732 I'll also test this out with the new |
||
} else { | ||
output[ rule.key ] = rule.value; | ||
} | ||
} ); | ||
|
||
return output; | ||
} | ||
|
||
function generateElementStyleSelector( | ||
selector, | ||
element, | ||
styles, | ||
pseudoSelector = '' | ||
) { | ||
return [ | ||
`.editor-styles-wrapper .${ selector } ${ element }${ pseudoSelector } {`, | ||
...Object.entries( styles ).map( | ||
( [ cssProperty, value ] ) => | ||
`\t${ kebabCase( cssProperty ) }: ${ value };` | ||
), | ||
'}', | ||
]; | ||
} | ||
|
||
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 };` | ||
// Default selectors | ||
...generateElementStyleSelector( | ||
selector, | ||
ELEMENTS[ element ], | ||
omit( elementStyles, [ 'states' ] ) | ||
), | ||
// State "pseudo selectors" | ||
...Object.keys( elementStyles?.states )?.flatMap( | ||
( stateKey ) => { | ||
return generateElementStyleSelector( | ||
selector, | ||
ELEMENTS[ element ], | ||
elementStyles?.states[ stateKey ], | ||
`:${ stateKey }` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a util function here which converts from stateKey ( |
||
); | ||
} | ||
), | ||
'}', | ||
].join( '\n' ); | ||
} | ||
return ''; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will now include any psuedo selectors