-
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
Global Styles: Fix push-to-global-styles clearing of attributes, border fallbacks, link hover colors, and behaviors #51621
Changes from all commits
2bef26a
724c49c
26d8df2
c3066c4
9376d76
aebc285
1fcce00
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 |
---|---|---|
|
@@ -12,7 +12,7 @@ import { | |
import { BaseControl, Button } from '@wordpress/components'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { | ||
__EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY, | ||
__EXPERIMENTAL_STYLE_PROPERTY, | ||
getBlockType, | ||
hasBlockSupport, | ||
} from '@wordpress/blocks'; | ||
|
@@ -27,15 +27,25 @@ import { useSupportedStyles } from '../../components/global-styles/hooks'; | |
import { unlock } from '../../lock-unlock'; | ||
|
||
const { | ||
cleanEmptyObject, | ||
GlobalStylesContext, | ||
__experimentalUseGlobalBehaviors: useGlobalBehaviors, | ||
__experimentalUseHasBehaviorsPanel: useHasBehaviorsPanel, | ||
} = unlock( blockEditorPrivateApis ); | ||
|
||
// Block Gap is a special case and isn't defined within the blocks | ||
// style properties config. We'll add it here to allow it to be pushed | ||
// to global styles as well. | ||
const STYLE_PROPERTY = { | ||
...__EXPERIMENTAL_STYLE_PROPERTY, | ||
blockGap: { value: [ 'spacing', 'blockGap' ] }, | ||
}; | ||
|
||
// TODO: Temporary duplication of constant in @wordpress/block-editor. Can be | ||
// removed by moving PushChangesToGlobalStylesControl to | ||
// @wordpress/block-editor. | ||
const STYLE_PATH_TO_CSS_VAR_INFIX = { | ||
'border.color': 'color', | ||
'color.background': 'color', | ||
'color.text': 'color', | ||
'elements.link.color.text': 'color', | ||
|
@@ -77,6 +87,7 @@ const STYLE_PATH_TO_CSS_VAR_INFIX = { | |
'elements.h6.typography.fontFamily': 'font-family', | ||
'elements.h6.color.gradient': 'gradient', | ||
'color.gradient': 'gradient', | ||
blockGap: 'spacing', | ||
'typography.fontSize': 'font-size', | ||
'typography.fontFamily': 'font-family', | ||
}; | ||
|
@@ -85,6 +96,7 @@ const STYLE_PATH_TO_CSS_VAR_INFIX = { | |
// removed by moving PushChangesToGlobalStylesControl to | ||
// @wordpress/block-editor. | ||
const STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE = { | ||
'border.color': 'borderColor', | ||
'color.background': 'backgroundColor', | ||
'color.text': 'textColor', | ||
'color.gradient': 'gradient', | ||
|
@@ -102,30 +114,127 @@ const getValueFromObjectPath = ( object, path ) => { | |
return value; | ||
}; | ||
|
||
function useChangesToPush( name, attributes ) { | ||
const flatBorderProperties = [ 'borderColor', 'borderWidth', 'borderStyle' ]; | ||
const sides = [ 'top', 'right', 'bottom', 'left' ]; | ||
|
||
function getBorderStyleChanges( border, presetColor, userStyle ) { | ||
if ( ! border && ! presetColor ) { | ||
return []; | ||
} | ||
|
||
const changes = [ | ||
...getFallbackBorderStyleChange( 'top', border, userStyle ), | ||
...getFallbackBorderStyleChange( 'right', border, userStyle ), | ||
...getFallbackBorderStyleChange( 'bottom', border, userStyle ), | ||
...getFallbackBorderStyleChange( 'left', border, userStyle ), | ||
]; | ||
|
||
// Handle a flat border i.e. all sides the same, CSS shorthand. | ||
const { color: customColor, style, width } = border || {}; | ||
const hasColorOrWidth = presetColor || customColor || width; | ||
|
||
if ( hasColorOrWidth && ! style ) { | ||
// Global Styles need individual side configurations to overcome | ||
// theme.json configurations which are per side as well. | ||
sides.forEach( ( side ) => { | ||
// Only add fallback border-style if global styles don't already | ||
// have something set. | ||
if ( ! userStyle?.[ side ]?.style ) { | ||
changes.push( { | ||
path: [ 'border', side, 'style' ], | ||
value: 'solid', | ||
} ); | ||
} | ||
} ); | ||
} | ||
|
||
return changes; | ||
} | ||
|
||
function getFallbackBorderStyleChange( side, border, globalBorderStyle ) { | ||
if ( ! border?.[ side ] || globalBorderStyle?.[ side ]?.style ) { | ||
return []; | ||
} | ||
|
||
const { color, style, width } = border[ side ]; | ||
const hasColorOrWidth = color || width; | ||
|
||
if ( ! hasColorOrWidth || style ) { | ||
return []; | ||
} | ||
|
||
return [ { path: [ 'border', side, 'style' ], value: 'solid' } ]; | ||
} | ||
|
||
function useChangesToPush( name, attributes, userConfig ) { | ||
const supports = useSupportedStyles( name ); | ||
const blockUserConfig = userConfig?.styles?.blocks?.[ name ]; | ||
|
||
return useMemo( | ||
() => | ||
supports.flatMap( ( key ) => { | ||
if ( ! STYLE_PROPERTY[ key ] ) { | ||
return []; | ||
return useMemo( () => { | ||
const changes = supports.flatMap( ( key ) => { | ||
if ( ! STYLE_PROPERTY[ key ] ) { | ||
return []; | ||
} | ||
const { value: path } = STYLE_PROPERTY[ key ]; | ||
const presetAttributeKey = path.join( '.' ); | ||
const presetAttributeValue = | ||
attributes[ | ||
STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE[ presetAttributeKey ] | ||
]; | ||
const value = presetAttributeValue | ||
? `var:preset|${ STYLE_PATH_TO_CSS_VAR_INFIX[ presetAttributeKey ] }|${ presetAttributeValue }` | ||
: getValueFromObjectPath( attributes.style, path ); | ||
|
||
// Links only have a single support entry but have two element | ||
// style properties, color and hover color. The following check | ||
// will add the hover color to the changes if required. | ||
if ( key === 'linkColor' ) { | ||
const linkChanges = value ? [ { path, value } ] : []; | ||
const hoverPath = [ | ||
'elements', | ||
'link', | ||
':hover', | ||
'color', | ||
'text', | ||
]; | ||
const hoverValue = getValueFromObjectPath( | ||
attributes.style, | ||
hoverPath | ||
); | ||
|
||
if ( hoverValue ) { | ||
linkChanges.push( { path: hoverPath, value: hoverValue } ); | ||
} | ||
const { value: path } = STYLE_PROPERTY[ key ]; | ||
const presetAttributeKey = path.join( '.' ); | ||
const presetAttributeValue = | ||
attributes[ | ||
STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE[ | ||
presetAttributeKey | ||
] | ||
]; | ||
const value = presetAttributeValue | ||
? `var:preset|${ STYLE_PATH_TO_CSS_VAR_INFIX[ presetAttributeKey ] }|${ presetAttributeValue }` | ||
: getValueFromObjectPath( attributes.style, path ); | ||
return value ? [ { path, value } ] : []; | ||
} ), | ||
[ supports, attributes ] | ||
); | ||
|
||
return linkChanges; | ||
} | ||
|
||
// The shorthand border styles can't be mapped directly as global | ||
// styles requires longhand config. | ||
if ( flatBorderProperties.includes( key ) && value ) { | ||
// The shorthand config path is included to clear the block attribute. | ||
const borderChanges = [ { path, value } ]; | ||
sides.forEach( ( side ) => { | ||
const currentPath = [ ...path ]; | ||
currentPath.splice( -1, 0, side ); | ||
borderChanges.push( { path: currentPath, value } ); | ||
} ); | ||
return borderChanges; | ||
} | ||
|
||
return value ? [ { path, value } ] : []; | ||
} ); | ||
|
||
// To ensure display of a visible border, global styles require a | ||
// default border style if a border color or width is present. | ||
getBorderStyleChanges( | ||
attributes.style?.border, | ||
attributes.borderColor, | ||
blockUserConfig?.border | ||
).forEach( ( change ) => changes.push( change ) ); | ||
|
||
return changes; | ||
}, [ supports, attributes, blockUserConfig ] ); | ||
} | ||
|
||
/** | ||
|
@@ -177,14 +286,15 @@ function PushChangesToGlobalStylesControl( { | |
attributes, | ||
setAttributes, | ||
} ) { | ||
const changes = useChangesToPush( name, attributes ); | ||
|
||
const hasBehaviorsPanel = useHasBehaviorsPanel( attributes, name, { | ||
blockSupportOnly: true, | ||
} ); | ||
|
||
const { user: userConfig, setUserConfig } = | ||
useContext( GlobalStylesContext ); | ||
|
||
const changes = useChangesToPush( name, attributes, userConfig ); | ||
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. We now pass the current user global styles to assist with determining if we need to provide a fallback border style to maintain a visible border. |
||
|
||
const { __unstableMarkNextChangeAsNotPersistent } = | ||
useDispatch( blockEditorStore ); | ||
const { createSuccessNotice } = useDispatch( noticesStore ); | ||
|
@@ -198,6 +308,7 @@ function PushChangesToGlobalStylesControl( { | |
if ( changes.length === 0 && ! userHasEditedBehaviors ) { | ||
return; | ||
} | ||
|
||
if ( changes.length > 0 ) { | ||
const { style: blockStyles } = attributes; | ||
|
||
|
@@ -213,12 +324,22 @@ function PushChangesToGlobalStylesControl( { | |
); | ||
} | ||
|
||
const newBlockAttributes = { | ||
borderColor: undefined, | ||
backgroundColor: undefined, | ||
textColor: undefined, | ||
gradient: undefined, | ||
fontSize: undefined, | ||
fontFamily: undefined, | ||
style: cleanEmptyObject( newBlockStyles ), | ||
}; | ||
|
||
// @wordpress/core-data doesn't support editing multiple entity types in | ||
// a single undo level. So for now, we disable @wordpress/core-data undo | ||
// tracking and implement our own Undo button in the snackbar | ||
// notification. | ||
__unstableMarkNextChangeAsNotPersistent(); | ||
setAttributes( { style: newBlockStyles } ); | ||
setAttributes( newBlockAttributes ); | ||
setUserConfig( () => newUserConfig, { undoIgnore: true } ); | ||
createSuccessNotice( | ||
sprintf( | ||
|
@@ -233,7 +354,7 @@ function PushChangesToGlobalStylesControl( { | |
label: __( 'Undo' ), | ||
onClick() { | ||
__unstableMarkNextChangeAsNotPersistent(); | ||
setAttributes( { style: blockStyles } ); | ||
setAttributes( attributes ); | ||
setUserConfig( () => userConfig, { | ||
undoIgnore: true, | ||
} ); | ||
|
@@ -243,8 +364,10 @@ function PushChangesToGlobalStylesControl( { | |
} | ||
); | ||
} | ||
|
||
if ( userHasEditedBehaviors ) { | ||
__unstableMarkNextChangeAsNotPersistent(); | ||
setAttributes( { behaviors: undefined } ); | ||
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. A good potential follow-up here might be to consolidate the updating of attributes and snackbar notices for both styles and behaviours. To keep the scope of this PR manageable for testing I've opted against this larger refactor. |
||
setBehavior( attributes.behaviors ); | ||
createSuccessNotice( | ||
sprintf( | ||
|
@@ -269,7 +392,19 @@ function PushChangesToGlobalStylesControl( { | |
} | ||
); | ||
} | ||
}, [ changes, attributes, userConfig, name ] ); | ||
}, [ | ||
__unstableMarkNextChangeAsNotPersistent, | ||
attributes, | ||
changes, | ||
createSuccessNotice, | ||
inheritedBehaviors, | ||
name, | ||
setAttributes, | ||
setBehavior, | ||
setUserConfig, | ||
userConfig, | ||
userHasEditedBehaviors, | ||
] ); | ||
|
||
return ( | ||
<BaseControl | ||
|
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.
There's an issue where if a theme.json configures longhand border properties and the user's global styles only configured shorthand border styles, the theme.json styles would incorrectly take precedence. As a result, Global Styles applies even shorthand borders to the longhand border properties.
This block of code maps shorthand block instance border styles to the longhand paths required by global styles.