diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js
index d093d3da55c8d6..9524564b487ecb 100644
--- a/packages/block-editor/src/hooks/background.js
+++ b/packages/block-editor/src/hooks/background.js
@@ -18,6 +18,7 @@ import {
__experimentalVStack as VStack,
DropZone,
FlexItem,
+ FocalPointPicker,
MenuItem,
VisuallyHidden,
__experimentalItemGroup as ItemGroup,
@@ -59,13 +60,17 @@ export function hasBackgroundImageValue( style ) {
/**
* Checks if there is a current value in the background size block support
- * attributes.
+ * attributes. Background size values include background size as well
+ * as background position.
*
* @param {Object} style Style attribute.
* @return {boolean} Whether or not the block has a background size value set.
*/
export function hasBackgroundSizeValue( style ) {
- return style?.background?.backgroundSize !== undefined;
+ return (
+ style?.background?.backgroundPosition !== undefined ||
+ style?.background?.backgroundSize !== undefined
+ );
}
/**
@@ -130,6 +135,7 @@ function resetBackgroundSize( style = {}, setAttributes ) {
...style,
background: {
...style?.background,
+ backgroundPosition: undefined,
backgroundRepeat: undefined,
backgroundSize: undefined,
},
@@ -367,6 +373,26 @@ function backgroundSizeHelpText( value ) {
return __( 'Set a fixed width.' );
}
+export const coordsToBackgroundPosition = ( value ) => {
+ if ( ! value || isNaN( value.x ) || isNaN( value.y ) ) {
+ return undefined;
+ }
+
+ return `${ value.x * 100 }% ${ value.y * 100 }%`;
+};
+
+export const backgroundPositionToCoords = ( value ) => {
+ if ( ! value ) {
+ return { x: undefined, y: undefined };
+ }
+
+ let [ x, y ] = value.split( ' ' ).map( ( v ) => parseFloat( v ) / 100 );
+ x = isNaN( x ) ? undefined : x;
+ y = isNaN( y ) ? x : y;
+
+ return { x, y };
+};
+
function BackgroundSizePanelItem( {
clientId,
isShownByDefault,
@@ -446,6 +472,18 @@ function BackgroundSizePanelItem( {
} );
};
+ const updateBackgroundPosition = ( next ) => {
+ setAttributes( {
+ style: cleanEmptyObject( {
+ ...style,
+ background: {
+ ...style?.background,
+ backgroundPosition: coordsToBackgroundPosition( next ),
+ },
+ } ),
+ } );
+ };
+
const toggleIsRepeated = () => {
setAttributes( {
style: cleanEmptyObject( {
@@ -471,6 +509,16 @@ function BackgroundSizePanelItem( {
resetAllFilter={ resetAllFilter }
panelId={ clientId }
>
+
{
+ it( 'should return the correct coordinates for a percentage value using 2-value syntax', () => {
+ expect( backgroundPositionToCoords( '25% 75%' ) ).toEqual( {
+ x: 0.25,
+ y: 0.75,
+ } );
+ } );
+
+ it( 'should return the correct coordinates for a percentage using 1-value syntax', () => {
+ expect( backgroundPositionToCoords( '50%' ) ).toEqual( {
+ x: 0.5,
+ y: 0.5,
+ } );
+ } );
+
+ it( 'should return undefined coords in given an empty value', () => {
+ expect( backgroundPositionToCoords( '' ) ).toEqual( {
+ x: undefined,
+ y: undefined,
+ } );
+ } );
+
+ it( 'should return undefined coords in given a string that cannot be converted', () => {
+ expect( backgroundPositionToCoords( 'apples' ) ).toEqual( {
+ x: undefined,
+ y: undefined,
+ } );
+ } );
+} );
+
+describe( 'coordsToBackgroundPosition', () => {
+ it( 'should return the correct background position for a set of coordinates', () => {
+ expect( coordsToBackgroundPosition( { x: 0.25, y: 0.75 } ) ).toBe(
+ '25% 75%'
+ );
+ } );
+
+ it( 'should return undefined if no coordinates are provided', () => {
+ expect( coordsToBackgroundPosition( {} ) ).toBeUndefined();
+ } );
+} );
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 94f69636b3e053..3c4dd1e29476c5 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -9,6 +9,7 @@
### Bug Fix
+- `FocalPointPicker`: Allow `PointerCircle` to render in a default centered position when x and y coordinates are undefined ([#58592](https://github.com/WordPress/gutenberg/pull/58592)).
- `DateTime`: Add a timezone offset value for display purposes. ([#56682](https://github.com/WordPress/gutenberg/pull/56682)).
- `Placeholder`: Fix Placeholder component padding when body text font size is changed ([#58323](https://github.com/WordPress/gutenberg/pull/58323)).
- `Placeholder`: Fix Global Styles typography settings bleeding into placeholder component ([#58303](https://github.com/WordPress/gutenberg/pull/58303)).
diff --git a/packages/components/src/focal-point-picker/index.tsx b/packages/components/src/focal-point-picker/index.tsx
index 1b4c4d9dff9665..33fd505f67fe17 100644
--- a/packages/components/src/focal-point-picker/index.tsx
+++ b/packages/components/src/focal-point-picker/index.tsx
@@ -217,8 +217,8 @@ export function FocalPointPicker( {
};
const focalPointPosition = {
- left: x * bounds.width,
- top: y * bounds.height,
+ left: x !== undefined ? x * bounds.width : 0.5 * bounds.width,
+ top: y !== undefined ? y * bounds.height : 0.5 * bounds.height,
};
const classes = classnames(
diff --git a/packages/style-engine/src/styles/background/index.ts b/packages/style-engine/src/styles/background/index.ts
index b9879ad2032a17..8ce8c7d577fb28 100644
--- a/packages/style-engine/src/styles/background/index.ts
+++ b/packages/style-engine/src/styles/background/index.ts
@@ -40,6 +40,18 @@ const backgroundImage = {
},
};
+const backgroundPosition = {
+ name: 'backgroundRepeat',
+ generate: ( style: Style, options: StyleOptions ) => {
+ return generateRule(
+ style,
+ options,
+ [ 'background', 'backgroundPosition' ],
+ 'backgroundPosition'
+ );
+ },
+};
+
const backgroundRepeat = {
name: 'backgroundRepeat',
generate: ( style: Style, options: StyleOptions ) => {
@@ -89,4 +101,9 @@ const backgroundSize = {
},
};
-export default [ backgroundImage, backgroundRepeat, backgroundSize ];
+export default [
+ backgroundImage,
+ backgroundPosition,
+ backgroundRepeat,
+ backgroundSize,
+];
diff --git a/packages/style-engine/src/test/index.js b/packages/style-engine/src/test/index.js
index 1727ed535897bc..b679775d3f37f4 100644
--- a/packages/style-engine/src/test/index.js
+++ b/packages/style-engine/src/test/index.js
@@ -229,6 +229,7 @@ describe( 'getCSSRules', () => {
source: 'file',
url: 'https://example.com/image.jpg',
},
+ backgroundPosition: '50% 50%',
backgroundRepeat: 'no-repeat',
backgroundSize: '300px',
},
@@ -384,6 +385,11 @@ describe( 'getCSSRules', () => {
key: 'backgroundImage',
value: "url( 'https://example.com/image.jpg' )",
},
+ {
+ selector: '.some-selector',
+ key: 'backgroundPosition',
+ value: '50% 50%',
+ },
{
selector: '.some-selector',
key: 'backgroundRepeat',