Skip to content

Commit

Permalink
Global Styles: Fix push-to-global-styles clearing of attributes, bord…
Browse files Browse the repository at this point in the history
…er fallbacks, link hover colors, and behaviors (#51621)
  • Loading branch information
aaronrobertshaw committed Aug 11, 2023
1 parent 042cf89 commit bf2ed0a
Showing 1 changed file with 162 additions and 27 deletions.
189 changes: 162 additions & 27 deletions packages/edit-site/src/hooks/push-changes-to-global-styles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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',
Expand Down Expand Up @@ -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',
};
Expand All @@ -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',
Expand All @@ -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 ] );
}

/**
Expand Down Expand Up @@ -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 );

const { __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
const { createSuccessNotice } = useDispatch( noticesStore );
Expand All @@ -198,6 +308,7 @@ function PushChangesToGlobalStylesControl( {
if ( changes.length === 0 && ! userHasEditedBehaviors ) {
return;
}

if ( changes.length > 0 ) {
const { style: blockStyles } = attributes;

Expand All @@ -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(
Expand All @@ -233,7 +354,7 @@ function PushChangesToGlobalStylesControl( {
label: __( 'Undo' ),
onClick() {
__unstableMarkNextChangeAsNotPersistent();
setAttributes( { style: blockStyles } );
setAttributes( attributes );
setUserConfig( () => userConfig, {
undoIgnore: true,
} );
Expand All @@ -243,8 +364,10 @@ function PushChangesToGlobalStylesControl( {
}
);
}

if ( userHasEditedBehaviors ) {
__unstableMarkNextChangeAsNotPersistent();
setAttributes( { behaviors: undefined } );
setBehavior( attributes.behaviors );
createSuccessNotice(
sprintf(
Expand All @@ -269,7 +392,19 @@ function PushChangesToGlobalStylesControl( {
}
);
}
}, [ changes, attributes, userConfig, name ] );
}, [
__unstableMarkNextChangeAsNotPersistent,
attributes,
changes,
createSuccessNotice,
inheritedBehaviors,
name,
setAttributes,
setBehavior,
setUserConfig,
userConfig,
userHasEditedBehaviors,
] );

return (
<BaseControl
Expand Down

0 comments on commit bf2ed0a

Please sign in to comment.