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

Global Styles: Fix push to global styles for 6.3.x #53624

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Changes from all commits
Commits
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
180 changes: 152 additions & 28 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 @@ -16,7 +16,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 @@ -30,14 +30,23 @@ import { store as noticesStore } from '@wordpress/notices';
import { useSupportedStyles } from '../../components/global-styles/hooks';
import { unlock } from '../../lock-unlock';

const { GlobalStylesContext, useBlockEditingMode } = unlock(
const { cleanEmptyObject, GlobalStylesContext, useBlockEditingMode } = 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 @@ -79,6 +88,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 @@ -87,6 +97,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 @@ -96,30 +107,124 @@ const STYLE_PATH_TO_PRESET_BLOCK_ATTRIBUTE = {

const SUPPORTED_STYLES = [ 'border', 'color', 'spacing', 'typography' ];

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( () => {
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 }`
: get( attributes.style, path );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original PR uses a new custom util (getValueFromObjectPath) to avoid the lodash use that was present in this file for 6.3.


return useMemo(
() =>
supports.flatMap( ( key ) => {
if ( ! STYLE_PROPERTY[ key ] ) {
return [];
// 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 = get( 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 }`
: get( attributes.style, path );
return value ? [ { path, value } ] : [];
} ),
[ supports, name, 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 ] );
}

function cloneDeep( object ) {
Expand All @@ -131,11 +236,11 @@ function PushChangesToGlobalStylesControl( {
attributes,
setAttributes,
} ) {
const changes = useChangesToPush( name, attributes );

const { user: userConfig, setUserConfig } =
useContext( GlobalStylesContext );

const changes = useChangesToPush( name, attributes, userConfig );

const { __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
const { createSuccessNotice } = useDispatch( noticesStore );
Expand All @@ -155,12 +260,22 @@ function PushChangesToGlobalStylesControl( {
set( newUserConfig, [ 'styles', 'blocks', name, ...path ], value );
}

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(
Expand All @@ -176,7 +291,7 @@ function PushChangesToGlobalStylesControl( {
label: __( 'Undo' ),
onClick() {
__unstableMarkNextChangeAsNotPersistent();
setAttributes( { style: blockStyles } );
setAttributes( attributes );
setUserConfig( () => userConfig, {
undoIgnore: true,
} );
Expand All @@ -185,7 +300,16 @@ function PushChangesToGlobalStylesControl( {
],
}
);
}, [ changes, attributes, userConfig, name ] );
}, [
__unstableMarkNextChangeAsNotPersistent,
attributes,
changes,
createSuccessNotice,
name,
setAttributes,
setUserConfig,
userConfig,
] );

return (
<BaseControl
Expand Down
Loading