diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php index 83d9508fb59565..543cbc127a9277 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php @@ -101,9 +101,13 @@ class WP_Theme_JSON_Gutenberg { 'value_key' => 'color', 'css_vars' => '--wp--preset--color--$slug', 'classes' => array( - '.has-$slug-color' => 'color', - '.has-$slug-background-color' => 'background-color', - '.has-$slug-border-color' => 'border-color', + '.has-$slug-color' => 'color', + '.has-$slug-background-color' => 'background-color', + '.has-$slug-border-color' => 'border-color', + '.has-$slug-border-top-color' => 'border-top-color', + '.has-$slug-border-right-color' => 'border-right-color', + '.has-$slug-border-bottom-color' => 'border-bottom-color', + '.has-$slug-border-left-color' => 'border-left-color', ), 'properties' => array( 'color', 'background-color', 'border-color' ), ), @@ -1440,7 +1444,7 @@ public function merge( $incoming ) { * Additionally, for some preset types, we also want to make sure the * values they introduce don't conflict with default values. We do so * by checking the incoming slugs for theme presets and compare them - * with the equivalent dfefault presets: if a slug is present as a default + * with the equivalent default presets: if a slug is present as a default * we remove it from the theme presets. */ $nodes = self::get_setting_nodes( $incoming_data ); @@ -1528,7 +1532,7 @@ public function get_svg_filters( $origins ) { } /** - * Returns whether a presets should be overriden or not. + * Returns whether a presets should be overridden or not. * * @param array $theme_json The theme.json like structure to inspect. * @param array $path Path to inspect. @@ -1543,8 +1547,8 @@ private static function should_override_preset( $theme_json, $path, $override ) // The relationship between whether to override the defaults // and whether the defaults are enabled is inverse: // - // - If defaults are enabled => theme presets should not be overriden - // - If defaults are disabled => theme presets should be overriden + // - If defaults are enabled => theme presets should not be overridden + // - If defaults are disabled => theme presets should be overridden // // For example, a theme sets defaultPalette to false, // making the default palette hidden from the user. @@ -1633,7 +1637,7 @@ private function get_name_from_defaults( $slug, $base_path ) { * Removes the preset values whose slug is equal to any of given slugs. * * @param array $node The node with the presets to validate. - * @param array $slugs The slugs that should not be overriden. + * @param array $slugs The slugs that should not be overridden. * * @return array The new node */ diff --git a/packages/block-editor/src/hooks/border-color.js b/packages/block-editor/src/hooks/border-color.js index 07b34fa6ba795a..a0524a2a35f750 100644 --- a/packages/block-editor/src/hooks/border-color.js +++ b/packages/block-editor/src/hooks/border-color.js @@ -68,7 +68,7 @@ export function BorderColorEdit( props ) { // Detect changes in the color attributes and update the colorValue to keep the // UI in sync. This is necessary for situations when border controls interact with - // eachother: eg, setting the border width to zero causes the color and style + // each other: eg, setting the border width to zero causes the color and style // selections to be cleared. useEffect( () => { setColorValue( @@ -171,19 +171,33 @@ function addAttributes( settings ) { return settings; } - // Allow blocks to specify default value if needed. - if ( settings.attributes.borderColor ) { + // Allow blocks to specify border color values if needed. + const { attributes } = settings; + + // Skip any adjustments if block already defines both border color + // attributes to set defaults etc. + if ( attributes.borderColor && attributes.sideBorderColors ) { return settings; } - // Add new borderColor attribute to block settings. + // If we are missing border color attribute definition, add it. + if ( ! attributes.borderColor ) { + return { + ...settings, + attributes: { + ...attributes, + borderColor: { type: 'string' }, + }, + }; + } + + // We are missing attribute for side border colors, add it to existing + // attribute definitions. return { ...settings, attributes: { - ...settings.attributes, - borderColor: { - type: 'string', - }, + ...attributes, + sideBorderColors: { type: 'object', default: null }, }, }; } @@ -205,19 +219,51 @@ function addSaveProps( props, blockType, attributes ) { return props; } + const borderClasses = getBorderClasses( attributes ); + const newClassName = classnames( props.className, borderClasses ); + + // If we are clearing the last of the previous classes in `className` + // set it to `undefined` to avoid rendering empty DOM attributes. + props.className = newClassName ? newClassName : undefined; + + return props; +} + +export function getBorderClasses( attributes ) { const { borderColor, style } = attributes; const borderColorClass = getColorClassName( 'border-color', borderColor ); - const newClassName = classnames( props.className, { + return classnames( { 'has-border-color': borderColor || style?.border?.color, [ borderColorClass ]: !! borderColorClass, + ...getSideBorderClasses( attributes ), } ); +} - // If we are clearing the last of the previous classes in `className` - // set it to `undefined` to avoid rendering empty DOM attributes. - props.className = newClassName ? newClassName : undefined; - - return props; +/** + * Generates a collection of CSS classes for the block's current border color + * selections. The results are intended to be further processed via a call + * through `classnames()`. + * + * @param {Object} attributes Block attributes. + * @return {Object} CSS classes for side border colors. + */ +function getSideBorderClasses( attributes ) { + const { sideBorderColors, style } = attributes; + const sides = [ 'top', 'right', 'bottom', 'left' ]; + + return sides.reduce( ( classes, side ) => { + const color = sideBorderColors?.[ side ]; + const hasColor = color || style?.border?.[ side ]?.color; + const baseClassName = `border-${ side }-color`; + const colorClass = getColorClassName( baseClassName, color ); + + return { + ...classes, + [ `has-${ baseClassName }` ]: hasColor, + [ colorClass ]: !! colorClass, + }; + }, {} ); } /** diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js index f578670daf0362..b96c13a6cbee5b 100644 --- a/packages/block-editor/src/hooks/border.js +++ b/packages/block-editor/src/hooks/border.js @@ -6,6 +6,7 @@ import { __experimentalToolsPanelItem as ToolsPanelItem, __experimentalBorderBoxControl as BorderBoxControl, isDefinedBorder, + hasSplitBorders, } from '@wordpress/components'; import { Platform } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -26,14 +27,17 @@ import { cleanEmptyObject } from './utils'; export const BORDER_SUPPORT_KEY = '__experimentalBorder'; const hasBorderValue = ( props ) => { - return isDefinedBorder( props.attributes.style?.border ); + const { borderColor, sideBorderColors, style } = props.attributes; + return isDefinedBorder( style?.border ) || borderColor || sideBorderColors; }; -// The border color, style, and width are omitted so the get undefined. The +// The border color, style, and width are omitted so they get undefined. The // border radius is separate and must retain its selection. const resetBorder = ( { attributes = {}, setAttributes } ) => { const { style } = attributes; setAttributes( { + borderColor: undefined, + sideBorderColors: undefined, style: { ...style, border: cleanEmptyObject( { @@ -43,9 +47,10 @@ const resetBorder = ( { attributes = {}, setAttributes } ) => { } ); }; -// TODO: Revisit this and check if it actually works as expected. const resetBorderFilter = ( newAttributes ) => ( { ...newAttributes, + borderColor: undefined, + sideBorderColors: undefined, style: { ...newAttributes.style, border: { @@ -54,12 +59,90 @@ const resetBorderFilter = ( newAttributes ) => ( { }, } ); -export function BorderPanel( props ) { +const getColorByProperty = ( colors, property, value ) => { + let matchedColor; + + colors.some( ( origin ) => + origin.colors.some( ( color ) => { + if ( color[ property ] === value ) { + matchedColor = color; + return true; + } + + return false; + } ) + ); + + return matchedColor; +}; + +export const getMultiOriginColor = ( { colors, namedColor, customColor } ) => { + // Search each origin (default, theme, or user) for matching color by name. + if ( namedColor ) { + const colorObject = getColorByProperty( colors, 'slug', namedColor ); + if ( colorObject ) { + return colorObject; + } + } + + // Skip if no custom color or matching named color. + if ( ! customColor ) { + return { color: undefined }; + } + + // Attempt to find color via custom color value or build new object. + const colorObject = getColorByProperty( colors, 'color', customColor ); + return colorObject ? colorObject : { color: customColor }; +}; + +const getBorderObject = ( attributes, colors ) => { const { - attributes: { style }, - clientId, - setAttributes, - } = props; + borderColor, + sideBorderColors, + style: { border: borderStyles }, + } = attributes; + + // If we have a named color for a flat border. Fetch that color object and + // apply that color's value to the color property within the style object. + if ( borderColor ) { + const { color } = getMultiOriginColor( { + colors, + namedColor: borderColor, + } ); + + return color ? { ...borderStyles, color } : borderStyles; + } + + // If we have named colors for the individual side borders, retrieve their + // related color objects and apply the real color values to the split + // border objects. + if ( sideBorderColors ) { + const hydratedBorderStyles = { ...borderStyles }; + + Object.entries( sideBorderColors ).forEach( + ( [ side, namedColor ] ) => { + const { color } = getMultiOriginColor( { colors, namedColor } ); + + if ( color ) { + hydratedBorderStyles[ side ] = { + ...hydratedBorderStyles[ side ], + color, + }; + } + } + ); + + return hydratedBorderStyles; + } + + // No named colors selected all color values if any should already be in + // the style's border object. + return borderStyles; +}; + +export function BorderPanel( props ) { + const { attributes, clientId, setAttributes } = props; + const { style } = attributes; const isDisabled = useIsBorderDisabled( props ); const isSupported = hasBorderSupport( props.name ); const { colors } = useMultipleOriginColorsAndGradients(); @@ -94,11 +177,55 @@ export function BorderPanel( props ) { const borderStyles = !! style?.border?.radius ? { radius: style?.border?.radius, ...newBorder } : newBorder; + + let newBorderColor; + let newSideBorderColors; + + if ( hasSplitBorders( borderStyles ) ) { + newSideBorderColors = {}; + + [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => { + if ( borderStyles[ side ]?.color ) { + const colorObject = getMultiOriginColor( { + colors, + customColor: borderStyles[ side ]?.color, + } ); + + if ( colorObject.slug ) { + // If we have a named color, set the sides named color + // attribute and clear the saved style objects color value. + newSideBorderColors[ side ] = colorObject.slug; + borderStyles[ side ].color = undefined; + } else { + // If we no longer have a named color, clear the + // side's named color attribute. + newSideBorderColors[ side ] = undefined; + } + } + } ); + } else if ( borderStyles?.color ) { + // We have a flat border configuration. Apply named color slug to + // `borderColor` attribute and clear color style property if found. + const customColor = borderStyles?.color; + const colorObject = getMultiOriginColor( { colors, customColor } ); + + if ( colorObject.slug ) { + newBorderColor = colorObject.slug; + borderStyles.color = undefined; + } + } + const newStyle = cleanEmptyObject( { ...style, border: borderStyles } ); - setAttributes( { style: newStyle } ); + setAttributes( { + style: newStyle, + borderColor: newBorderColor, + sideBorderColors: newSideBorderColors, + } ); }; + const hydratedBorder = getBorderObject( attributes, colors ); + return ( { ( isWidthSupported || isColorSupported ) && ( @@ -116,7 +243,7 @@ export function BorderPanel( props ) { showColor={ isColorSupported } showStyle={ isStyleSupported } showWidth={ isWidthSupported } - value={ style?.border } + value={ hydratedBorder } __experimentalHasMultipleOrigins={ true } __experimentalIsRenderedInSidebar={ true } /> diff --git a/packages/block-editor/src/hooks/use-border-props.js b/packages/block-editor/src/hooks/use-border-props.js index d62d68365fe1b3..cb78f0bfc148e4 100644 --- a/packages/block-editor/src/hooks/use-border-props.js +++ b/packages/block-editor/src/hooks/use-border-props.js @@ -1,46 +1,29 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * Internal dependencies */ import { getInlineStyles } from './style'; -import { - getColorClassName, - getColorObjectByAttributeValues, -} from '../components/colors'; -import useSetting from '../components/use-setting'; +import { getBorderClasses } from './border-color'; +import { getMultiOriginColor } from './border'; +import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients'; // This utility is intended to assist where the serialization of the border // block support is being skipped for a block but the border related CSS classes // & styles still need to be generated so they can be applied to inner elements. -const EMPTY_ARRAY = []; - /** * Provides the CSS class names and inline styles for a block's border support * attributes. * - * @param {Object} attributes Block attributes. - * @param {string} attributes.borderColor Selected named border color. - * @param {Object} attributes.style Block's styles attribute. - * + * @param {Object} attributes Block attributes. * @return {Object} Border block support derived CSS classes & styles. */ -export function getBorderClassesAndStyles( { borderColor, style } ) { - const borderStyles = style?.border || {}; - const borderClass = getColorClassName( 'border-color', borderColor ); - - const className = classnames( { - [ borderClass ]: !! borderClass, - 'has-border-color': borderColor || style?.border?.color, - } ); +export function getBorderClassesAndStyles( attributes ) { + const border = attributes.style?.border || {}; + const className = getBorderClasses( attributes ); return { className: className || undefined, - style: getInlineStyles( { border: borderStyles } ), + style: getInlineStyles( { border } ), }; } @@ -56,19 +39,42 @@ export function getBorderClassesAndStyles( { borderColor, style } ) { * @return {Object} ClassName & style props from border block support. */ export function useBorderProps( attributes ) { - const colors = useSetting( 'color.palette' ) || EMPTY_ARRAY; + const { colors } = useMultipleOriginColorsAndGradients(); const borderProps = getBorderClassesAndStyles( attributes ); + const { borderColor, sideBorderColors } = attributes; - // Force inline style to apply border color when themes do not load their - // color stylesheets in the editor. - if ( attributes.borderColor ) { - const borderColorObject = getColorObjectByAttributeValues( + // Force inline styles to apply named border colors when themes do not load + // their color stylesheets in the editor. + if ( borderColor ) { + const borderColorObject = getMultiOriginColor( { colors, - attributes.borderColor - ); + namedColor: borderColor, + } ); borderProps.style.borderColor = borderColorObject.color; } + if ( ! sideBorderColors ) { + return borderProps; + } + + const sides = { + top: 'borderTopColor', + right: 'borderRightColor', + bottom: 'borderBottomColor', + left: 'borderLeftColor', + }; + + Object.entries( sides ).forEach( ( [ side, property ] ) => { + if ( sideBorderColors[ side ] ) { + const { color } = getMultiOriginColor( { + colors, + namedColor: sideBorderColors[ side ], + } ); + + borderProps.style[ property ] = color; + } + } ); + return borderProps; } diff --git a/packages/block-library/src/common.scss b/packages/block-library/src/common.scss index 7d2892f57cead9..c2891b937b7d39 100644 --- a/packages/block-library/src/common.scss +++ b/packages/block-library/src/common.scss @@ -116,6 +116,18 @@ html :where(.has-border-color) { border-style: solid; } +html :where(.has-border-top-color) { + border-top-style: solid; +} +html :where(.has-border-right-color) { + border-right-style: solid; +} +html :where(.has-border-bottom-color) { + border-bottom-style: solid; +} +html :where(.has-border-left-color) { + border-left-style: solid; +} html :where([style*="border-width"]) { border-style: solid; diff --git a/packages/edit-site/src/components/global-styles/utils.js b/packages/edit-site/src/components/global-styles/utils.js index aac3e40a1a9612..64130ef857a43d 100644 --- a/packages/edit-site/src/components/global-styles/utils.js +++ b/packages/edit-site/src/components/global-styles/utils.js @@ -36,6 +36,22 @@ export const PRESET_METADATA = [ classSuffix: 'border-color', propertyName: 'border-color', }, + { + classSuffix: 'border-top-color', + propertyName: 'border-top-color', + }, + { + classSuffix: 'border-right-color', + propertyName: 'border-right-color', + }, + { + classSuffix: 'border-bottom-color', + propertyName: 'border-bottom-color', + }, + { + classSuffix: 'border-left-color', + propertyName: 'border-left-color', + }, ], }, {