diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md
index 0c852324ba59d8..033724c8216259 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -115,6 +115,8 @@ Settings related to dimensions.
| Property | Type | Default | Props |
| --- | --- | --- |--- |
| aspectRatio | boolean | false | |
+| defaultAspectRatios | boolean | true | |
+| aspectRatios | array | | name, ratio, slug |
| minHeight | boolean | false | |
---
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 72c9057aa2af01..bd2b780e55a45c 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -123,9 +123,19 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.0.0 Replaced `override` with `prevent_override` and updated the
* `prevent_override` value for `color.duotone` to use `color.defaultDuotone`.
* @since 6.2.0 Added 'shadow' presets.
+ * @since 6.6.0 Added `aspectRatios`.
* @var array
*/
const PRESETS_METADATA = array(
+ array(
+ 'path' => array( 'dimensions', 'aspectRatios' ),
+ 'prevent_override' => array( 'dimensions', 'defaultAspectRatios' ),
+ 'use_default_names' => false,
+ 'value_key' => 'ratio',
+ 'css_vars' => '--wp--preset--aspect-ratio--$slug',
+ 'classes' => array(),
+ 'properties' => array( 'aspect-ratio' ),
+ ),
array(
'path' => array( 'color', 'palette' ),
'prevent_override' => array( 'color', 'defaultPalette' ),
@@ -397,8 +407,10 @@ class WP_Theme_JSON_Gutenberg {
),
'custom' => null,
'dimensions' => array(
- 'aspectRatio' => null,
- 'minHeight' => null,
+ 'aspectRatio' => null,
+ 'aspectRatios' => null,
+ 'defaultAspectRatios' => null,
+ 'minHeight' => null,
),
'layout' => array(
'contentSize' => null,
@@ -483,7 +495,7 @@ class WP_Theme_JSON_Gutenberg {
* updated `blockGap` to be allowed at any level.
* @since 6.2.0 Added `outline`, and `minHeight` properties.
* @since 6.6.0 Added `background` sub properties to top-level only.
- *
+ * @since 6.6.0 Added `dimensions.aspectRatio`.
* @var array
*/
const VALID_STYLES = array(
diff --git a/lib/theme.json b/lib/theme.json
index 90a5d975e68d65..49aa2abb08005b 100644
--- a/lib/theme.json
+++ b/lib/theme.json
@@ -190,6 +190,46 @@
],
"text": true
},
+ "dimensions": {
+ "defaultAspectRatios": true,
+ "aspectRatios": [
+ {
+ "name": "Square - 1:1",
+ "slug": "square",
+ "ratio": "1"
+ },
+ {
+ "name": "Standard - 4:3",
+ "slug": "4-3",
+ "ratio": "4/3"
+ },
+ {
+ "name": "Portrait - 3:4",
+ "slug": "3-4",
+ "ratio": "3/4"
+ },
+ {
+ "name": "Classic - 3:2",
+ "slug": "3-2",
+ "ratio": "3/2"
+ },
+ {
+ "name": "Classic Portrait - 2:3",
+ "slug": "2-3",
+ "ratio": "2/3"
+ },
+ {
+ "name": "Wide - 16:9",
+ "slug": "16-9",
+ "ratio": "16/9"
+ },
+ {
+ "name": "Tall - 9:16",
+ "slug": "9-16",
+ "ratio": "9/16"
+ }
+ ]
+ },
"shadow": {
"defaultPresets": true,
"presets": [
diff --git a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
index 5ff35ae0e0c888..e38a01e199b792 100644
--- a/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
+++ b/packages/block-editor/src/components/dimensions-tool/aspect-ratio-tool.js
@@ -6,75 +6,14 @@ import {
__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
-
/**
- * @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
+ * Internal dependencies
*/
+import { useSettings } from '../use-settings';
/**
- * @type {SelectControlProps[]}
+ * @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
*/
-export const DEFAULT_ASPECT_RATIO_OPTIONS = [
- {
- label: _x( 'Original', 'Aspect ratio option for dimensions control' ),
- value: 'auto',
- },
- {
- label: _x(
- 'Square - 1:1',
- 'Aspect ratio option for dimensions control'
- ),
- value: '1',
- },
- {
- label: _x(
- 'Standard - 4:3',
- 'Aspect ratio option for dimensions control'
- ),
- value: '4/3',
- },
- {
- label: _x(
- 'Portrait - 3:4',
- 'Aspect ratio option for dimensions control'
- ),
- value: '3/4',
- },
- {
- label: _x(
- 'Classic - 3:2',
- 'Aspect ratio option for dimensions control'
- ),
- value: '3/2',
- },
- {
- label: _x(
- 'Classic Portrait - 2:3',
- 'Aspect ratio option for dimensions control'
- ),
- value: '2/3',
- },
- {
- label: _x(
- 'Wide - 16:9',
- 'Aspect ratio option for dimensions control'
- ),
- value: '16/9',
- },
- {
- label: _x(
- 'Tall - 9:16',
- 'Aspect ratio option for dimensions control'
- ),
- value: '9/16',
- },
- {
- label: _x( 'Custom', 'Aspect ratio option for dimensions control' ),
- value: 'custom',
- disabled: true,
- hidden: true,
- },
-];
/**
* @callback AspectRatioToolPropsOnChange
@@ -96,14 +35,48 @@ export default function AspectRatioTool( {
panelId,
value,
onChange = () => {},
- options = DEFAULT_ASPECT_RATIO_OPTIONS,
- defaultValue = DEFAULT_ASPECT_RATIO_OPTIONS[ 0 ].value,
+ options,
+ defaultValue = 'auto',
hasValue,
isShownByDefault = true,
} ) {
// Match the CSS default so if the value is used directly in CSS it will look correct in the control.
const displayValue = value ?? 'auto';
+ const [ defaultRatios, themeRatios, showDefaultRatios ] = useSettings(
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
+
+ const themeOptions = themeRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const defaultOptions = defaultRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const aspectRatioOptions = [
+ {
+ label: _x(
+ 'Original',
+ 'Aspect ratio option for dimensions control'
+ ),
+ value: 'auto',
+ },
+ ...( showDefaultRatios ? defaultOptions : [] ),
+ ...( themeOptions ? themeOptions : [] ),
+ {
+ label: _x( 'Custom', 'Aspect ratio option for dimensions control' ),
+ value: 'custom',
+ disabled: true,
+ hidden: true,
+ },
+ ];
+
return (
diff --git a/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js b/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
index a15c9f42b9e8ed..3f48a634bdbcea 100644
--- a/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
+++ b/packages/block-editor/src/components/image-editor/aspect-ratio-dropdown.js
@@ -8,34 +8,70 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
+import { useSettings } from '../use-settings';
import { POPOVER_PROPS } from './constants';
import { useImageEditingContext } from './context';
-function AspectGroup( { aspectRatios, isDisabled, label, onClick, value } ) {
+function AspectRatioGroup( {
+ aspectRatios,
+ isDisabled,
+ label,
+ onClick,
+ value,
+} ) {
return (
- { aspectRatios.map( ( { title, aspect } ) => (
+ { aspectRatios.map( ( { name, slug, ratio } ) => (
) ) }
);
}
+export function ratioToNumber( str ) {
+ // TODO: support two-value aspect ratio?
+ // https://css-tricks.com/almanac/properties/a/aspect-ratio/#aa-it-can-take-two-values
+ const [ a, b, ...rest ] = str.split( '/' ).map( Number );
+ if (
+ a <= 0 ||
+ b <= 0 ||
+ Number.isNaN( a ) ||
+ Number.isNaN( b ) ||
+ rest.length
+ ) {
+ return NaN;
+ }
+ return b ? a / b : a;
+}
+
+function presetRatioAsNumber( { ratio, ...rest } ) {
+ return {
+ ratio: ratioToNumber( ratio ),
+ ...rest,
+ };
+}
+
export default function AspectRatioDropdown( { toggleProps } ) {
const { isInProgress, aspect, setAspect, defaultAspect } =
useImageEditingContext();
+ const [ defaultRatios, themeRatios, showDefaultRatios ] = useSettings(
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
+
return (
{ ( { onClose } ) => (
<>
- {
setAspect( newAspect );
@@ -56,61 +92,57 @@ export default function AspectRatioDropdown( { toggleProps } ) {
aspectRatios={ [
// All ratios should be mirrored in AspectRatioTool in @wordpress/block-editor.
{
- title: __( 'Original' ),
+ slug: 'original',
+ name: __( 'Original' ),
aspect: defaultAspect,
},
- {
- title: __( 'Square' ),
- aspect: 1,
- },
- ] }
- />
- {
- setAspect( newAspect );
- onClose();
- } }
- value={ aspect }
- aspectRatios={ [
- {
- title: __( '16:9' ),
- aspect: 16 / 9,
- },
- {
- title: __( '4:3' ),
- aspect: 4 / 3,
- },
- {
- title: __( '3:2' ),
- aspect: 3 / 2,
- },
- ] }
- />
- {
- setAspect( newAspect );
- onClose();
- } }
- value={ aspect }
- aspectRatios={ [
- {
- title: __( '9:16' ),
- aspect: 9 / 16,
- },
- {
- title: __( '3:4' ),
- aspect: 3 / 4,
- },
- {
- title: __( '2:3' ),
- aspect: 2 / 3,
- },
+ ...( showDefaultRatios
+ ? defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio === 1 )
+ : [] ),
] }
/>
+ { themeRatios?.length > 0 && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ themeRatios }
+ />
+ ) }
+ { showDefaultRatios && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio > 1 ) }
+ />
+ ) }
+ { showDefaultRatios && (
+ {
+ setAspect( newAspect );
+ onClose();
+ } }
+ value={ aspect }
+ aspectRatios={ defaultRatios
+ .map( presetRatioAsNumber )
+ .filter( ( { ratio } ) => ratio < 1 ) }
+ />
+ ) }
>
) }
diff --git a/packages/block-editor/src/components/image-editor/index.js b/packages/block-editor/src/components/image-editor/index.js
index cfd912bb2827ca..133f79732bdbd5 100644
--- a/packages/block-editor/src/components/image-editor/index.js
+++ b/packages/block-editor/src/components/image-editor/index.js
@@ -6,11 +6,11 @@ import { ToolbarGroup, ToolbarItem } from '@wordpress/components';
/**
* Internal dependencies
*/
+import AspectRatioDropdown from './aspect-ratio-dropdown';
import BlockControls from '../block-controls';
import ImageEditingProvider from './context';
import Cropper from './cropper';
import ZoomDropdown from './zoom-dropdown';
-import AspectRatioDropdown from './aspect-ratio-dropdown';
import RotationButton from './rotation-button';
import FormControls from './form-controls';
diff --git a/packages/block-editor/src/components/image-editor/test/index.js b/packages/block-editor/src/components/image-editor/test/index.js
new file mode 100644
index 00000000000000..9f0f3491667a8d
--- /dev/null
+++ b/packages/block-editor/src/components/image-editor/test/index.js
@@ -0,0 +1,22 @@
+/**
+ * Internal dependencies
+ */
+import { ratioToNumber } from '../aspect-ratio-dropdown';
+
+test( 'ratioToNumber', () => {
+ expect( ratioToNumber( '1/1' ) ).toBe( 1 );
+ expect( ratioToNumber( '1' ) ).toBe( 1 );
+ expect( ratioToNumber( '11/11' ) ).toBe( 1 );
+ expect( ratioToNumber( '16/9' ) ).toBe( 16 / 9 );
+ expect( ratioToNumber( '4/3' ) ).toBe( 4 / 3 );
+ expect( ratioToNumber( '3/2' ) ).toBe( 3 / 2 );
+ expect( ratioToNumber( '2/1' ) ).toBe( 2 );
+ expect( ratioToNumber( '1/2' ) ).toBe( 1 / 2 );
+ expect( ratioToNumber( '2/3' ) ).toBe( 2 / 3 );
+ expect( ratioToNumber( '3/4' ) ).toBe( 3 / 4 );
+ expect( ratioToNumber( '9/16' ) ).toBe( 9 / 16 );
+ expect( ratioToNumber( '1/16' ) ).toBe( 1 / 16 );
+ expect( ratioToNumber( '16/1' ) ).toBe( 16 );
+ expect( ratioToNumber( '1/9' ) ).toBe( 1 / 9 );
+ expect( ratioToNumber( 'auto' ) ).toBe( NaN );
+} );
diff --git a/packages/block-library/src/post-featured-image/dimension-controls.js b/packages/block-library/src/post-featured-image/dimension-controls.js
index b64b3299fc96ba..c8e8c0005cfef5 100644
--- a/packages/block-library/src/post-featured-image/dimension-controls.js
+++ b/packages/block-library/src/post-featured-image/dimension-controls.js
@@ -57,7 +57,13 @@ const DimensionControls = ( {
setAttributes,
media,
} ) => {
- const [ availableUnits ] = useSettings( 'spacing.units' );
+ const [ availableUnits, defaultRatios, themeRatios, showDefaultRatios ] =
+ useSettings(
+ 'spacing.units',
+ 'dimensions.aspectRatios.default',
+ 'dimensions.aspectRatios.theme',
+ 'dimensions.defaultAspectRatios'
+ );
const units = useCustomUnits( {
availableUnits: availableUnits || [ 'px', '%', 'vw', 'em', 'rem' ],
} );
@@ -93,6 +99,28 @@ const DimensionControls = ( {
const showScaleControl =
height || ( aspectRatio && aspectRatio !== 'auto' );
+ const themeOptions = themeRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const defaultOptions = defaultRatios?.map( ( { name, ratio } ) => ( {
+ label: name,
+ value: ratio,
+ } ) );
+
+ const aspectRatioOptions = [
+ {
+ label: _x(
+ 'Original',
+ 'Aspect ratio option for dimensions control'
+ ),
+ value: 'auto',
+ },
+ ...( showDefaultRatios ? defaultOptions : [] ),
+ ...( themeOptions ? themeOptions : [] ),
+ ];
+
return (
<>
setAttributes( { aspectRatio: nextAspectRatio } )
}
diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js
index 08b4e084e2ec9b..68877c280d4dcf 100644
--- a/packages/blocks/src/api/constants.js
+++ b/packages/blocks/src/api/constants.js
@@ -288,6 +288,7 @@ export const __EXPERIMENTAL_PATHS_WITH_OVERRIDE = {
'color.duotone': true,
'color.gradients': true,
'color.palette': true,
+ 'dimensions.aspectRatios': true,
'typography.fontSizes': true,
'spacing.spacingSizes': true,
};
diff --git a/schemas/json/theme.json b/schemas/json/theme.json
index ccd2ee7f2ce53e..949b16c8b9d6ab 100644
--- a/schemas/json/theme.json
+++ b/schemas/json/theme.json
@@ -286,6 +286,32 @@
"type": "boolean",
"default": false
},
+ "defaultAspectRatios": {
+ "description": "Allow users to choose aspect ratios from the default set of aspect ratios.",
+ "type": "boolean",
+ "default": true
+ },
+ "aspectRatios": {
+ "description": "Allow users to define aspect ratios for some blocks.",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "Name of the aspect ratio preset.",
+ "type": "string"
+ },
+ "slug": {
+ "description": "Kebab-case unique identifier for the aspect ratio preset.",
+ "type": "string"
+ },
+ "ratio": {
+ "description": "Aspect ratio expressed as a division or decimal.",
+ "type": "string"
+ }
+ }
+ }
+ },
"minHeight": {
"description": "Allow users to set custom minimum height.",
"type": "boolean",
@@ -2274,6 +2300,7 @@
},
"background": {},
"color": {},
+ "dimensions": {},
"layout": {},
"lightbox": {},
"spacing": {},
diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js
index 24fff3e579f682..314834816388b2 100644
--- a/test/e2e/specs/editor/blocks/image.spec.js
+++ b/test/e2e/specs/editor/blocks/image.spec.js
@@ -327,7 +327,7 @@ test.describe( 'Image', () => {
await editor.clickBlockToolbarButton( 'Crop' );
await editor.clickBlockToolbarButton( 'Aspect Ratio' );
await page.click(
- 'role=menu[name="Aspect Ratio"i] >> role=menuitemradio[name="16:9"i]'
+ 'role=menu[name="Aspect Ratio"i] >> role=menuitemradio[name="Wide - 16:9"i]'
);
await editor.clickBlockToolbarButton( 'Apply' );