diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js
new file mode 100644
index 00000000000000..83979b2845c4be
--- /dev/null
+++ b/packages/block-editor/src/components/global-styles/border-panel.js
@@ -0,0 +1,285 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ __experimentalBorderBoxControl as BorderBoxControl,
+ __experimentalHasSplitBorders as hasSplitBorders,
+ __experimentalIsDefinedBorder as isDefinedBorder,
+ __experimentalToolsPanel as ToolsPanel,
+ __experimentalToolsPanelItem as ToolsPanelItem,
+} from '@wordpress/components';
+import { useCallback, useMemo } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import BorderRadiusControl from '../border-radius-control';
+import { useColorsPerOrigin } from './hooks';
+import { getValueFromVariable } from './utils';
+
+export function useHasBorderPanel( settings ) {
+ const controls = [
+ useHasBorderColorControl( settings ),
+ useHasBorderRadiusControl( settings ),
+ useHasBorderStyleControl( settings ),
+ useHasBorderWidthControl( settings ),
+ ];
+
+ return controls.some( Boolean );
+}
+
+function useHasBorderColorControl( settings ) {
+ return settings?.border?.color;
+}
+
+function useHasBorderRadiusControl( settings ) {
+ return settings?.border?.radius;
+}
+
+function useHasBorderStyleControl( settings ) {
+ return settings?.border?.style;
+}
+
+function useHasBorderWidthControl( settings ) {
+ return settings?.border?.width;
+}
+
+function applyFallbackStyle( border ) {
+ if ( ! border ) {
+ return border;
+ }
+
+ if ( ! border.style && ( border.color || border.width ) ) {
+ return { ...border, style: 'solid' };
+ }
+
+ return border;
+}
+
+function applyAllFallbackStyles( border ) {
+ if ( ! border ) {
+ return border;
+ }
+
+ if ( hasSplitBorders( border ) ) {
+ return {
+ top: applyFallbackStyle( border.top ),
+ right: applyFallbackStyle( border.right ),
+ bottom: applyFallbackStyle( border.bottom ),
+ left: applyFallbackStyle( border.left ),
+ };
+ }
+
+ return applyFallbackStyle( border );
+}
+
+function BorderToolsPanel( {
+ resetAllFilter,
+ onChange,
+ value,
+ panelId,
+ children,
+} ) {
+ const resetAll = () => {
+ const updatedValue = resetAllFilter( value );
+ onChange( updatedValue );
+ };
+
+ return (
+
+ { children }
+
+ );
+}
+
+const DEFAULT_CONTROLS = {
+ radius: true,
+ color: true,
+ width: true,
+};
+
+export default function BorderPanel( {
+ as: Wrapper = BorderToolsPanel,
+ value,
+ onChange,
+ inheritedValue = value,
+ settings,
+ panelId,
+ defaultControls = DEFAULT_CONTROLS,
+} ) {
+ const colors = useColorsPerOrigin( settings );
+ const decodeValue = ( rawValue ) =>
+ getValueFromVariable( { settings }, '', rawValue );
+ const encodeColorValue = ( colorValue ) => {
+ const allColors = colors.flatMap(
+ ( { colors: originColors } ) => originColors
+ );
+ const colorObject = allColors.find(
+ ( { color } ) => color === colorValue
+ );
+ return colorObject
+ ? 'var:preset|color|' + colorObject.slug
+ : colorValue;
+ };
+ const decodeColorValue = useCallback(
+ ( colorValue ) => {
+ const allColors = colors.flatMap(
+ ( { colors: originColors } ) => originColors
+ );
+ const colorObject = allColors.find(
+ ( { slug } ) => colorValue === 'var:preset|color|' + slug
+ );
+ return colorObject ? colorObject.color : colorValue;
+ },
+ [ colors ]
+ );
+ const border = useMemo( () => {
+ if ( hasSplitBorders( inheritedValue?.border ) ) {
+ const borderValue = { ...inheritedValue?.border };
+ [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
+ borderValue[ side ] = {
+ ...borderValue[ side ],
+ color: decodeColorValue( borderValue[ side ]?.color ),
+ };
+ } );
+ return borderValue;
+ }
+ return {
+ ...inheritedValue?.border,
+ color: inheritedValue?.border?.color
+ ? decodeColorValue( inheritedValue?.border?.color )
+ : undefined,
+ };
+ }, [ inheritedValue?.border, decodeColorValue ] );
+ const setBorder = ( newBorder ) =>
+ onChange( { ...value, border: newBorder } );
+ const showBorderColor = useHasBorderColorControl( settings );
+ const showBorderStyle = useHasBorderStyleControl( settings );
+ const showBorderWidth = useHasBorderWidthControl( settings );
+
+ // Border radius.
+ const showBorderRadius = useHasBorderRadiusControl( settings );
+ const borderRadiusValues = decodeValue( border?.radius );
+ const setBorderRadius = ( newBorderRadius ) =>
+ setBorder( { ...border, radius: newBorderRadius } );
+ const hasBorderRadius = () => {
+ const borderValues = value?.border?.radius;
+ if ( typeof borderValues === 'object' ) {
+ return Object.entries( borderValues ).some( Boolean );
+ }
+ return !! borderValues;
+ };
+
+ const resetBorder = () => {
+ if ( hasBorderRadius() ) {
+ return setBorder( { radius: value?.border?.radius } );
+ }
+
+ setBorder( undefined );
+ };
+
+ const onBorderChange = ( newBorder ) => {
+ // Ensure we have a visible border style when a border width or
+ // color is being selected.
+ const newBorderWithStyle = applyAllFallbackStyles( newBorder );
+
+ // As we can't conditionally generate styles based on if other
+ // style properties have been set we need to force split border
+ // definitions for user set border styles. Border radius is derived
+ // from the same property i.e. `border.radius` if it is a string
+ // that is used. The longhand border radii styles are only generated
+ // if that property is an object.
+ //
+ // For borders (color, style, and width) those are all properties on
+ // the `border` style property. This means if the theme.json defined
+ // split borders and the user condenses them into a flat border or
+ // vice-versa we'd get both sets of styles which would conflict.
+ const updatedBorder = ! hasSplitBorders( newBorderWithStyle )
+ ? {
+ top: newBorderWithStyle,
+ right: newBorderWithStyle,
+ bottom: newBorderWithStyle,
+ left: newBorderWithStyle,
+ }
+ : {
+ color: null,
+ style: null,
+ width: null,
+ ...newBorderWithStyle,
+ };
+
+ [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
+ updatedBorder[ side ] = {
+ ...updatedBorder[ side ],
+ color: encodeColorValue( updatedBorder[ side ]?.color ),
+ };
+ } );
+
+ // As radius is maintained separately to color, style, and width
+ // maintain its value. Undefined values here will be cleaned when
+ // global styles are saved.
+ setBorder( { radius: border?.radius, ...updatedBorder } );
+ };
+
+ const resetAllFilter = useCallback( ( previousValue ) => {
+ return {
+ ...previousValue,
+ border: undefined,
+ };
+ }, [] );
+
+ const showBorderByDefault =
+ defaultControls?.color || defaultControls?.width;
+
+ return (
+
+ { ( showBorderWidth || showBorderColor ) && (
+ isDefinedBorder( value?.border ) }
+ label={ __( 'Border' ) }
+ onDeselect={ () => resetBorder() }
+ isShownByDefault={ showBorderByDefault }
+ panelId={ panelId }
+ >
+
+
+ ) }
+ { showBorderRadius && (
+ setBorderRadius( undefined ) }
+ isShownByDefault={ defaultControls.radius }
+ panelId={ panelId }
+ >
+ {
+ setBorderRadius( newValue || undefined );
+ } }
+ />
+
+ ) }
+
+ );
+}
diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js
index f483a9208e6e11..2414f3d4b4b9c7 100644
--- a/packages/block-editor/src/components/global-styles/hooks.js
+++ b/packages/block-editor/src/components/global-styles/hooks.js
@@ -10,6 +10,7 @@ import { get, set } from 'lodash';
import { useContext, useCallback, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { store as blocksStore } from '@wordpress/blocks';
+import { _x } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -303,6 +304,67 @@ export function useSettingsForBlockElement(
};
}
+ [ 'radius', 'color', 'style', 'width' ].forEach( ( key ) => {
+ if (
+ ! supportedStyles.includes(
+ 'border' + key.charAt( 0 ).toUpperCase() + key.slice( 1 )
+ )
+ ) {
+ updatedSettings.border = {
+ ...updatedSettings.border,
+ [ key ]: false,
+ };
+ }
+ } );
+
return updatedSettings;
}, [ parentSettings, supportedStyles, supports ] );
}
+
+export function useColorsPerOrigin( settings ) {
+ const customColors = settings?.color?.palette?.custom;
+ const themeColors = settings?.color?.palette?.theme;
+ const defaultColors = settings?.color?.palette?.default;
+ const shouldDisplayDefaultColors = settings?.color?.defaultPalette;
+
+ return useMemo( () => {
+ const result = [];
+ if ( themeColors && themeColors.length ) {
+ result.push( {
+ name: _x(
+ 'Theme',
+ 'Indicates this palette comes from the theme.'
+ ),
+ colors: themeColors,
+ } );
+ }
+ if (
+ shouldDisplayDefaultColors &&
+ defaultColors &&
+ defaultColors.length
+ ) {
+ result.push( {
+ name: _x(
+ 'Default',
+ 'Indicates this palette comes from WordPress.'
+ ),
+ colors: defaultColors,
+ } );
+ }
+ if ( customColors && customColors.length ) {
+ result.push( {
+ name: _x(
+ 'Custom',
+ 'Indicates this palette is created by the user.'
+ ),
+ colors: customColors,
+ } );
+ }
+ return result;
+ }, [
+ customColors,
+ themeColors,
+ defaultColors,
+ shouldDisplayDefaultColors,
+ ] );
+}
diff --git a/packages/block-editor/src/components/global-styles/index.js b/packages/block-editor/src/components/global-styles/index.js
index 9c06af05b2e8b6..372f56fcead0d4 100644
--- a/packages/block-editor/src/components/global-styles/index.js
+++ b/packages/block-editor/src/components/global-styles/index.js
@@ -3,6 +3,7 @@ export {
useGlobalSetting,
useGlobalStyle,
useSettingsForBlockElement,
+ useColorsPerOrigin,
} from './hooks';
export { useGlobalStylesOutput } from './use-global-styles-output';
export { GlobalStylesContext } from './context';
@@ -14,3 +15,4 @@ export {
default as DimensionsPanel,
useHasDimensionsPanel,
} from './dimensions-panel';
+export { default as BorderPanel, useHasBorderPanel } from './border-panel';
diff --git a/packages/block-editor/src/hooks/border-radius.js b/packages/block-editor/src/hooks/border-radius.js
deleted file mode 100644
index 6b247e141b6818..00000000000000
--- a/packages/block-editor/src/hooks/border-radius.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Internal dependencies
- */
-import BorderRadiusControl from '../components/border-radius-control';
-import { cleanEmptyObject } from './utils';
-import { removeBorderAttribute } from './border';
-
-/**
- * Inspector control panel containing the border radius related configuration.
- *
- * @param {Object} props Block properties.
- *
- * @return {WPElement} Border radius edit element.
- */
-export function BorderRadiusEdit( props ) {
- const {
- attributes: { style },
- setAttributes,
- } = props;
-
- const onChange = ( newRadius ) => {
- const newStyle = cleanEmptyObject( {
- ...style,
- border: {
- ...style?.border,
- radius: newRadius,
- },
- } );
-
- setAttributes( { style: newStyle } );
- };
-
- return (
-
- );
-}
-
-/**
- * Checks if there is a current value in the border radius block support
- * attributes.
- *
- * @param {Object} props Block props.
- * @return {boolean} Whether or not the block has a border radius value set.
- */
-export function hasBorderRadiusValue( props ) {
- const borderRadius = props.attributes.style?.border?.radius;
-
- if ( typeof borderRadius === 'object' ) {
- return Object.entries( borderRadius ).some( Boolean );
- }
-
- return !! borderRadius;
-}
-
-/**
- * Resets the border radius block support attributes. This can be used when
- * disabling the border radius support controls for a block via a progressive
- * discovery panel.
- *
- * @param {Object} props Block props.
- * @param {Object} props.attributes Block's attributes.
- * @param {Object} props.setAttributes Function to set block's attributes.
- */
-export function resetBorderRadius( { attributes = {}, setAttributes } ) {
- const { style } = attributes;
- setAttributes( { style: removeBorderAttribute( style, 'radius' ) } );
-}
diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js
index d99416e6cd6651..a5f08b64515b01 100644
--- a/packages/block-editor/src/hooks/border.js
+++ b/packages/block-editor/src/hooks/border.js
@@ -7,66 +7,29 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { getBlockSupport } from '@wordpress/blocks';
-import {
- __experimentalBorderBoxControl as BorderBoxControl,
- __experimentalHasSplitBorders as hasSplitBorders,
- __experimentalIsDefinedBorder as isDefinedBorder,
- __experimentalToolsPanelItem as ToolsPanelItem,
-} from '@wordpress/components';
+import { __experimentalHasSplitBorders as hasSplitBorders } from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
-import { Platform } from '@wordpress/element';
+import { Platform, useCallback, useMemo } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
-import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import {
- BorderRadiusEdit,
- hasBorderRadiusValue,
- resetBorderRadius,
-} from './border-radius';
import { getColorClassName } from '../components/colors';
import InspectorControls from '../components/inspector-controls';
import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients';
-import useSetting from '../components/use-setting';
-import { cleanEmptyObject, shouldSkipSerialization } from './utils';
+import {
+ cleanEmptyObject,
+ shouldSkipSerialization,
+ useBlockSettings,
+} from './utils';
+import {
+ useHasBorderPanel,
+ BorderPanel as StylesBorderPanel,
+} from '../components/global-styles';
export const BORDER_SUPPORT_KEY = '__experimentalBorder';
-const borderSides = [ 'top', 'right', 'bottom', 'left' ];
-
-const hasBorderValue = ( props ) => {
- const { borderColor, style } = props.attributes;
- return isDefinedBorder( style?.border ) || !! borderColor;
-};
-
-// 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,
- style: {
- ...style,
- border: cleanEmptyObject( {
- radius: style?.border?.radius,
- } ),
- },
- } );
-};
-
-const resetBorderFilter = ( newAttributes ) => ( {
- ...newAttributes,
- borderColor: undefined,
- style: {
- ...newAttributes.style,
- border: {
- radius: newAttributes.style?.border?.radius,
- },
- },
-} );
-
const getColorByProperty = ( colors, property, value ) => {
let matchedColor;
@@ -103,51 +66,6 @@ export const getMultiOriginColor = ( { colors, namedColor, customColor } ) => {
return colorObject ? colorObject : { color: customColor };
};
-const getBorderObject = ( attributes, colors ) => {
- const { borderColor, style } = attributes;
- const { border: borderStyles } = style || {};
-
- // 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;
- }
-
- // Individual side border color slugs are stored within the border style
- // object. If we don't have a border styles object we have nothing further
- // to hydrate.
- if ( ! borderStyles ) {
- return 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.
- const hydratedBorderStyles = { ...borderStyles };
- borderSides.forEach( ( side ) => {
- const colorSlug = getColorSlugFromVariable(
- hydratedBorderStyles[ side ]?.color
- );
- if ( colorSlug ) {
- const { color } = getMultiOriginColor( {
- colors,
- namedColor: colorSlug,
- } );
- hydratedBorderStyles[ side ] = {
- ...hydratedBorderStyles[ side ],
- color,
- };
- }
- } );
-
- return hydratedBorderStyles;
-};
-
function getColorSlugFromVariable( value ) {
const namedColor = /var:preset\|color\|(.+)/.exec( value );
if ( namedColor && namedColor[ 1 ] ) {
@@ -156,150 +74,101 @@ function getColorSlugFromVariable( value ) {
return null;
}
-export function BorderPanel( props ) {
- const { attributes, clientId, setAttributes } = props;
- const { style } = attributes;
- const { colors } = useMultipleOriginColorsAndGradients();
-
- const isSupported = hasBorderSupport( props.name );
- const isColorSupported =
- useSetting( 'border.color' ) && hasBorderSupport( props.name, 'color' );
- const isRadiusSupported =
- useSetting( 'border.radius' ) &&
- hasBorderSupport( props.name, 'radius' );
- const isStyleSupported =
- useSetting( 'border.style' ) && hasBorderSupport( props.name, 'style' );
- const isWidthSupported =
- useSetting( 'border.width' ) && hasBorderSupport( props.name, 'width' );
-
- const isDisabled = [
- ! isColorSupported,
- ! isRadiusSupported,
- ! isStyleSupported,
- ! isWidthSupported,
- ].every( Boolean );
-
- if ( isDisabled || ! isSupported ) {
- return null;
+function styleToAttributes( style ) {
+ if ( hasSplitBorders( style?.border ) ) {
+ return {
+ style,
+ borderColor: undefined,
+ };
}
- const defaultBorderControls = getBlockSupport( props.name, [
- BORDER_SUPPORT_KEY,
- '__experimentalDefaultControls',
- ] );
+ const borderColorValue = style?.border?.color;
+ const borderColorSlug = borderColorValue?.startsWith( 'var:preset|color|' )
+ ? borderColorSlug.substring( 'var:preset|color|'.length )
+ : undefined;
+ const updatedStyle = { ...style };
+ updatedStyle.border = {
+ ...updatedStyle.border,
+ color: borderColorSlug ? undefined : borderColorValue,
+ };
+ return {
+ style: cleanEmptyObject( updatedStyle ),
+ borderColor: borderColorSlug,
+ };
+}
- const showBorderByDefault =
- defaultBorderControls?.color || defaultBorderControls?.width;
-
- const onBorderChange = ( newBorder ) => {
- // Filter out named colors and apply them to appropriate block
- // attributes so that CSS classes can be used to apply those colors.
- // e.g. has-primary-border-top-color.
-
- let newBorderStyles = { ...newBorder };
- let newBorderColor;
-
- if ( hasSplitBorders( newBorder ) ) {
- // For each side check if the side has a color value set
- // If so, determine if it belongs to a named color, in which case
- // we update the color property.
- //
- // This deliberately overwrites `newBorderStyles` to avoid mutating
- // the passed object which causes problems otherwise.
- newBorderStyles = {
- top: { ...newBorder.top },
- right: { ...newBorder.right },
- bottom: { ...newBorder.bottom },
- left: { ...newBorder.left },
+function attributesToStyle( attributes ) {
+ if ( hasSplitBorders( attributes.style?.border ) ) {
+ return attributes.style;
+ }
+ return {
+ ...attributes.style,
+ border: {
+ ...attributes.style?.border,
+ color: attributes.borderColor
+ ? 'var:preset|color|' + attributes.borderColor
+ : attributes.style?.border?.color,
+ },
+ };
+}
+
+function BordersInspectorControl( { children, resetAllFilter } ) {
+ const attributesResetAllFilter = useCallback(
+ ( attributes ) => {
+ const existingStyle = attributesToStyle( attributes );
+ const updatedStyle = resetAllFilter( existingStyle );
+ return {
+ ...attributes,
+ ...styleToAttributes( updatedStyle ),
};
+ },
+ [ resetAllFilter ]
+ );
- borderSides.forEach( ( side ) => {
- if ( newBorder[ side ]?.color ) {
- const colorObject = getMultiOriginColor( {
- colors,
- customColor: newBorder[ side ]?.color,
- } );
-
- if ( colorObject.slug ) {
- newBorderStyles[
- side
- ].color = `var:preset|color|${ colorObject.slug }`;
- }
- }
- } );
- } else if ( newBorder?.color ) {
- // We have a flat border configuration. Apply named color slug to
- // `borderColor` attribute and clear color style property if found.
- const customColor = newBorder?.color;
- const colorObject = getMultiOriginColor( { colors, customColor } );
-
- if ( colorObject.slug ) {
- newBorderColor = colorObject.slug;
- newBorderStyles.color = undefined;
- }
- }
+ return (
+
+ { children }
+
+ );
+}
- // Ensure previous border radius styles are maintained and clean
- // overall result for empty objects or properties.
- const newStyle = cleanEmptyObject( {
- ...style,
- border: { radius: style?.border?.radius, ...newBorderStyles },
+export function BorderPanel( props ) {
+ const { clientId, name, attributes, setAttributes } = props;
+ const settings = useBlockSettings( name );
+ const isEnabled = useHasBorderPanel( settings );
+ const value = useMemo( () => {
+ return attributesToStyle( {
+ style: attributes.style,
+ borderColor: attributes.borderColor,
} );
+ }, [ attributes.style, attributes.borderColor ] );
- setAttributes( {
- style: newStyle,
- borderColor: newBorderColor,
- } );
+ const onChange = ( newStyle ) => {
+ setAttributes( styleToAttributes( newStyle ) );
};
- const hydratedBorder = getBorderObject( attributes, colors );
+ if ( ! isEnabled ) {
+ return null;
+ }
+
+ const defaultControls = getBlockSupport( props.name, [
+ BORDER_SUPPORT_KEY,
+ '__experimentalDefaultControls',
+ ] );
return (
-
- { ( isWidthSupported || isColorSupported ) && (
- hasBorderValue( props ) }
- label={ __( 'Border' ) }
- onDeselect={ () => resetBorder( props ) }
- isShownByDefault={ showBorderByDefault }
- resetAllFilter={ resetBorderFilter }
- panelId={ clientId }
- >
-
-
- ) }
- { isRadiusSupported && (
- hasBorderRadiusValue( props ) }
- label={ __( 'Radius' ) }
- onDeselect={ () => resetBorderRadius( props ) }
- isShownByDefault={ defaultBorderControls?.radius }
- resetAllFilter={ ( newAttributes ) => ( {
- ...newAttributes,
- style: {
- ...newAttributes.style,
- border: {
- ...newAttributes.style?.border,
- radius: undefined,
- },
- },
- } ) }
- panelId={ clientId }
- >
-
-
- ) }
-
+
);
}
diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js
index 87d666e7cb7695..3684598997a6a8 100644
--- a/packages/block-editor/src/hooks/utils.js
+++ b/packages/block-editor/src/hooks/utils.js
@@ -212,9 +212,27 @@ export function useBlockSettings( name, parentLayout ) {
const units = useSetting( 'spacing.units' );
const minHeight = useSetting( 'dimensions.minHeight' );
const layout = useSetting( 'layout' );
+ const borderColor = useSetting( 'border.color' );
+ const borderRadius = useSetting( 'border.radius' );
+ const borderStyle = useSetting( 'border.style' );
+ const borderWidth = useSetting( 'border.width' );
+ const customColorsEnabled = useSetting( 'color.custom' );
+ const customColors = useSetting( 'color.palette.custom' );
+ const themeColors = useSetting( 'color.palette.theme' );
+ const defaultColors = useSetting( 'color.palette.default' );
+ const defaultPalette = useSetting( 'color.defaultPalette' );
const rawSettings = useMemo( () => {
return {
+ color: {
+ palette: {
+ custom: customColors,
+ theme: themeColors,
+ default: defaultColors,
+ },
+ defaultPalette,
+ custom: customColorsEnabled,
+ },
typography: {
fontFamilies: {
custom: fontFamilies,
@@ -239,6 +257,12 @@ export function useBlockSettings( name, parentLayout ) {
blockGap,
units,
},
+ border: {
+ color: borderColor,
+ radius: borderRadius,
+ style: borderStyle,
+ width: borderWidth,
+ },
dimensions: {
minHeight,
},
@@ -263,6 +287,15 @@ export function useBlockSettings( name, parentLayout ) {
minHeight,
layout,
parentLayout,
+ borderColor,
+ borderRadius,
+ borderStyle,
+ borderWidth,
+ customColorsEnabled,
+ customColors,
+ themeColors,
+ defaultColors,
+ defaultPalette,
] );
return useSettingsForBlockElement( rawSettings, name );
diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js
index 36ddedb60cf4ac..76a750343418c5 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -35,6 +35,7 @@ export {
export { default as __experimentalStyleProvider } from './style-provider';
export { default as BaseControl } from './base-control';
+export { hasSplitBorders as __experimentalHasSplitBorders } from './border-box-control/utils';
export { default as TextareaControl } from './textarea-control';
export { default as PanelBody } from './panel/body';
export { default as PanelActions } from './panel/actions';
diff --git a/packages/edit-site/src/components/global-styles/border-panel.js b/packages/edit-site/src/components/global-styles/border-panel.js
index 07e6254e4b34ee..0926e6edd03386 100644
--- a/packages/edit-site/src/components/global-styles/border-panel.js
+++ b/packages/edit-site/src/components/global-styles/border-panel.js
@@ -1,215 +1,40 @@
/**
* WordPress dependencies
*/
-import {
- __experimentalBorderRadiusControl as BorderRadiusControl,
- privateApis as blockEditorPrivateApis,
-} from '@wordpress/block-editor';
-import {
- __experimentalBorderBoxControl as BorderBoxControl,
- __experimentalHasSplitBorders as hasSplitBorders,
- __experimentalIsDefinedBorder as isDefinedBorder,
- __experimentalToolsPanel as ToolsPanel,
- __experimentalToolsPanelItem as ToolsPanelItem,
-} from '@wordpress/components';
-import { useCallback } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
-import { useSupportedStyles, useColorsPerOrigin } from './hooks';
import { unlock } from '../../private-apis';
-const { useGlobalSetting, useGlobalStyle } = unlock( blockEditorPrivateApis );
-
-export function useHasBorderPanel( name ) {
- const controls = [
- useHasBorderColorControl( name ),
- useHasBorderRadiusControl( name ),
- useHasBorderStyleControl( name ),
- useHasBorderWidthControl( name ),
- ];
-
- return controls.some( Boolean );
-}
-
-function useHasBorderColorControl( name ) {
- const supports = useSupportedStyles( name );
- return (
- useGlobalSetting( 'border.color', name )[ 0 ] &&
- supports.includes( 'borderColor' )
- );
-}
-
-function useHasBorderRadiusControl( name ) {
- const supports = useSupportedStyles( name );
- return (
- useGlobalSetting( 'border.radius', name )[ 0 ] &&
- supports.includes( 'borderRadius' )
- );
-}
-
-function useHasBorderStyleControl( name ) {
- const supports = useSupportedStyles( name );
- return (
- useGlobalSetting( 'border.style', name )[ 0 ] &&
- supports.includes( 'borderStyle' )
- );
-}
-
-function useHasBorderWidthControl( name ) {
- const supports = useSupportedStyles( name );
- return (
- useGlobalSetting( 'border.width', name )[ 0 ] &&
- supports.includes( 'borderWidth' )
- );
-}
-
-function applyFallbackStyle( border ) {
- if ( ! border ) {
- return border;
- }
-
- if ( ! border.style && ( border.color || border.width ) ) {
- return { ...border, style: 'solid' };
- }
-
- return border;
-}
-
-function applyAllFallbackStyles( border ) {
- if ( ! border ) {
- return border;
- }
-
- if ( hasSplitBorders( border ) ) {
- return {
- top: applyFallbackStyle( border.top ),
- right: applyFallbackStyle( border.right ),
- bottom: applyFallbackStyle( border.bottom ),
- left: applyFallbackStyle( border.left ),
- };
- }
-
- return applyFallbackStyle( border );
-}
+const {
+ useGlobalStyle,
+ useGlobalSetting,
+ useSettingsForBlockElement,
+ BorderPanel: StylesBorderPanel,
+} = unlock( blockEditorPrivateApis );
export default function BorderPanel( { name, variation = '' } ) {
- const prefix = variation ? `variations.${ variation }.` : '';
- // To better reflect if the user has customized a value we need to
- // ensure the style value being checked is from the `user` origin.
- const [ userBorderStyles ] = useGlobalStyle(
- `${ prefix }border`,
- name,
- 'user'
- );
- const [ border, setBorder ] = useGlobalStyle( `${ prefix }border`, name );
- const colors = useColorsPerOrigin( name );
-
- const showBorderColor = useHasBorderColorControl( name );
- const showBorderStyle = useHasBorderStyleControl( name );
- const showBorderWidth = useHasBorderWidthControl( name );
-
- // Border radius.
- const showBorderRadius = useHasBorderRadiusControl( name );
- const [ borderRadiusValues, setBorderRadius ] = useGlobalStyle(
- `${ prefix }border.radius`,
- name
- );
- const hasBorderRadius = () => {
- const borderValues = userBorderStyles?.radius;
- if ( typeof borderValues === 'object' ) {
- return Object.entries( borderValues ).some( Boolean );
- }
- return !! borderValues;
- };
-
- const resetBorder = () => {
- if ( hasBorderRadius() ) {
- return setBorder( { radius: userBorderStyles.radius } );
- }
-
- setBorder( undefined );
- };
-
- const resetAll = useCallback( () => setBorder( undefined ), [ setBorder ] );
- const onBorderChange = useCallback(
- ( newBorder ) => {
- // Ensure we have a visible border style when a border width or
- // color is being selected.
- const newBorderWithStyle = applyAllFallbackStyles( newBorder );
-
- // As we can't conditionally generate styles based on if other
- // style properties have been set we need to force split border
- // definitions for user set border styles. Border radius is derived
- // from the same property i.e. `border.radius` if it is a string
- // that is used. The longhand border radii styles are only generated
- // if that property is an object.
- //
- // For borders (color, style, and width) those are all properties on
- // the `border` style property. This means if the theme.json defined
- // split borders and the user condenses them into a flat border or
- // vice-versa we'd get both sets of styles which would conflict.
- const updatedBorder = ! hasSplitBorders( newBorderWithStyle )
- ? {
- top: newBorderWithStyle,
- right: newBorderWithStyle,
- bottom: newBorderWithStyle,
- left: newBorderWithStyle,
- }
- : {
- color: null,
- style: null,
- width: null,
- ...newBorderWithStyle,
- };
+ let prefixParts = [];
+ if ( variation ) {
+ prefixParts = [ 'variations', variation ].concat( prefixParts );
+ }
+ const prefix = prefixParts.join( '.' );
- // As radius is maintained separately to color, style, and width
- // maintain its value. Undefined values here will be cleaned when
- // global styles are saved.
- setBorder( { radius: border?.radius, ...updatedBorder } );
- },
- [ setBorder, border?.radius ]
- );
+ const [ style ] = useGlobalStyle( prefix, name, 'user', false );
+ const [ inheritedStyle, setStyle ] = useGlobalStyle( prefix, name, 'all', {
+ shouldDecodeEncode: false,
+ } );
+ const [ rawSettings ] = useGlobalSetting( '', name );
+ const settings = useSettingsForBlockElement( rawSettings, name );
return (
-
- { ( showBorderWidth || showBorderColor ) && (
- isDefinedBorder( userBorderStyles ) }
- label={ __( 'Border' ) }
- onDeselect={ () => resetBorder() }
- isShownByDefault={ true }
- >
-
-
- ) }
- { showBorderRadius && (
- setBorderRadius( undefined ) }
- isShownByDefault={ true }
- >
- {
- setBorderRadius( value || undefined );
- } }
- />
-
- ) }
-
+
);
}
diff --git a/packages/edit-site/src/components/global-styles/context-menu.js b/packages/edit-site/src/components/global-styles/context-menu.js
index fd9565412ede76..89fd262d9ade4c 100644
--- a/packages/edit-site/src/components/global-styles/context-menu.js
+++ b/packages/edit-site/src/components/global-styles/context-menu.js
@@ -27,7 +27,6 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
-import { useHasBorderPanel } from './border-panel';
import { useHasColorPanel } from './color-utils';
import { useHasFilterPanel } from './filter-utils';
import { useHasVariationsPanel } from './variations-panel';
@@ -40,6 +39,7 @@ import { unlock } from '../../private-apis';
const {
useHasDimensionsPanel,
useHasTypographyPanel,
+ useHasBorderPanel,
useGlobalSetting,
useSettingsForBlockElement,
} = unlock( blockEditorPrivateApis );
@@ -49,7 +49,7 @@ function ContextMenu( { name, parentMenu = '' } ) {
const settings = useSettingsForBlockElement( rawSettings, name );
const hasTypographyPanel = useHasTypographyPanel( settings );
const hasColorPanel = useHasColorPanel( name );
- const hasBorderPanel = useHasBorderPanel( name );
+ const hasBorderPanel = useHasBorderPanel( settings );
const hasEffectsPanel = useHasShadowControl( name );
const hasFilterPanel = useHasFilterPanel( name );
const hasDimensionsPanel = useHasDimensionsPanel( settings );
diff --git a/packages/edit-site/src/components/global-styles/hooks.js b/packages/edit-site/src/components/global-styles/hooks.js
index e1c5370cd630a1..690cbd3cbcbe6e 100644
--- a/packages/edit-site/src/components/global-styles/hooks.js
+++ b/packages/edit-site/src/components/global-styles/hooks.js
@@ -18,7 +18,8 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import { unlock } from '../../private-apis';
import { useSelect } from '@wordpress/data';
-const { useGlobalSetting } = unlock( blockEditorPrivateApis );
+const { useGlobalSetting, useColorsPerOrigin: useColorsPerOriginFromSettings } =
+ unlock( blockEditorPrivateApis );
// Enable colord's a11y plugin.
extend( [ a11yPlugin ] );
@@ -31,41 +32,16 @@ export function useColorsPerOrigin( name ) {
'color.defaultPalette'
);
- return useMemo( () => {
- const result = [];
- if ( themeColors && themeColors.length ) {
- result.push( {
- name: _x(
- 'Theme',
- 'Indicates this palette comes from the theme.'
- ),
- colors: themeColors,
- } );
- }
- if (
- shouldDisplayDefaultColors &&
- defaultColors &&
- defaultColors.length
- ) {
- result.push( {
- name: _x(
- 'Default',
- 'Indicates this palette comes from WordPress.'
- ),
- colors: defaultColors,
- } );
- }
- if ( customColors && customColors.length ) {
- result.push( {
- name: _x(
- 'Custom',
- 'Indicates this palette is created by the user.'
- ),
- colors: customColors,
- } );
- }
- return result;
- }, [ customColors, themeColors, defaultColors ] );
+ return useColorsPerOriginFromSettings( {
+ color: {
+ palette: {
+ custom: customColors,
+ theme: themeColors,
+ default: defaultColors,
+ },
+ defaultPalette: shouldDisplayDefaultColors,
+ },
+ } );
}
export function useGradientsPerOrigin( name ) {
diff --git a/packages/edit-site/src/components/global-styles/screen-block-list.js b/packages/edit-site/src/components/global-styles/screen-block-list.js
index 07a6f8554af84a..e85b3956f3e372 100644
--- a/packages/edit-site/src/components/global-styles/screen-block-list.js
+++ b/packages/edit-site/src/components/global-styles/screen-block-list.js
@@ -20,7 +20,6 @@ import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
*/
-import { useHasBorderPanel } from './border-panel';
import { useHasColorPanel } from './color-utils';
import { useHasVariationsPanel } from './variations-panel';
import ScreenHeader from './header';
@@ -30,6 +29,7 @@ import { unlock } from '../../private-apis';
const {
useHasDimensionsPanel,
useHasTypographyPanel,
+ useHasBorderPanel,
useGlobalSetting,
useSettingsForBlockElement,
} = unlock( blockEditorPrivateApis );
@@ -62,7 +62,7 @@ function BlockMenuItem( { block } ) {
const settings = useSettingsForBlockElement( rawSettings, block.name );
const hasTypographyPanel = useHasTypographyPanel( settings );
const hasColorPanel = useHasColorPanel( block.name );
- const hasBorderPanel = useHasBorderPanel( block.name );
+ const hasBorderPanel = useHasBorderPanel( settings );
const hasDimensionsPanel = useHasDimensionsPanel( settings );
const hasLayoutPanel = hasBorderPanel || hasDimensionsPanel;
const hasVariationsPanel = useHasVariationsPanel( block.name );
diff --git a/packages/edit-site/src/components/global-styles/screen-border.js b/packages/edit-site/src/components/global-styles/screen-border.js
index 6a5578e20fcefa..f700f2676448f8 100644
--- a/packages/edit-site/src/components/global-styles/screen-border.js
+++ b/packages/edit-site/src/components/global-styles/screen-border.js
@@ -2,17 +2,24 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import ScreenHeader from './header';
-import BorderPanel, { useHasBorderPanel } from './border-panel';
+import BorderPanel from './border-panel';
import BlockPreviewPanel from './block-preview-panel';
import { getVariationClassName } from './utils';
+import { unlock } from '../../private-apis';
+
+const { useHasBorderPanel, useGlobalSetting, useSettingsForBlockElement } =
+ unlock( blockEditorPrivateApis );
function ScreenBorder( { name, variation = '' } ) {
- const hasBorderPanel = useHasBorderPanel( name );
+ const [ rawSettings ] = useGlobalSetting( '', name );
+ const settings = useSettingsForBlockElement( rawSettings, name );
+ const hasBorderPanel = useHasBorderPanel( settings );
const variationClassName = getVariationClassName( variation );
return (
<>