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

Add image block aspect ratio control #51545

Merged
merged 116 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
a3796d8
Simplify ImageSizeControl by using Auto as a placeholder
Jun 8, 2023
d931e04
Rename imageWidth and imageHeight props to naturalWidth and naturalHe…
Jun 8, 2023
767f942
Convert NumberControl onChange values to Numbers
Jun 8, 2023
f2d51bb
Simplify LatestPostsEdit to use updated ImageSizeControl
Jun 8, 2023
423127c
Add JSDoc types for debugging
Jun 8, 2023
0fda291
Remove unnecessary noop
Jun 8, 2023
ff2d89c
Fix possible undefined values in NumberControl onChange
Jun 8, 2023
5df76a0
Fix onChangeImage param type which may be undefined
Jun 8, 2023
6912a67
Rename OnChange callback prop
Jun 8, 2023
1926516
Inline JSDoc props instead of new object
Jun 8, 2023
1bc922a
Simplify handing undefined and NaN in onChange
Jun 8, 2023
7cf50dc
Revert prop name change since this isn't a private API
Jun 8, 2023
f0a3730
Add a privateApis export for experimental ImageSizeControl
Jun 8, 2023
0c8c296
Use the privateApis version of ImageSizeControl
Jun 8, 2023
f46967c
Add deprecation notice to the original component
Jun 8, 2023
7c33045
Revert image-size-control and create image-dimensions-control instead
Jun 8, 2023
d13b872
Re-add deprecation notice to image-size-control
Jun 8, 2023
d6021c9
Try making a whole new component
Jun 9, 2023
64177d9
Revert changes to image, latest-posts, and media-text blocks
Jun 14, 2023
5105298
Organize and update the dimensions tool panel item
Jun 15, 2023
6d9ba06
Reword size help text
Jun 15, 2023
5b80d23
Reorganize into reusable components
Jun 15, 2023
0e327c6
Add stories for other individual tools
Jun 15, 2023
78f42e5
Update stories path
Jun 15, 2023
b2837f2
Remove SelectControl __next prop
Jun 15, 2023
2e5d3da
Pass through isShownByDefault to ResolutionTool
Jun 15, 2023
1f777ce
Remove unused scss
Jun 15, 2023
50514be
Deprecate experimental ImageSizeControl
Jun 15, 2023
c44d8a4
Simplify ScaleTool onChange
Jun 15, 2023
aaa00fe
Add better defaults for value and onChange
Jun 15, 2023
45b5f4f
Fix circular dependency
Jun 15, 2023
02baab7
Update comment about auto and custom aspect ratios
Jun 15, 2023
8c6da27
Add JSDoc types for ScaleTool
Jun 15, 2023
251b241
Add JSDoc types for WidthHeightTool
Jun 15, 2023
b123a68
Add default value and onChange for WidthHeightTool
Jun 15, 2023
bd6b16e
Remove unused import
Jun 15, 2023
7243878
Add aspectRatio to image block attributes
Jun 15, 2023
8074a11
Add scale to image block attributes
Jun 16, 2023
44d7ac5
Update JSDoc comment
ajlende Jun 19, 2023
0a2c29f
Add dimensions tool to image block
ajlende Jun 19, 2023
da2de5e
Rename naturalAspectRatio for clarity
ajlende Jun 19, 2023
ca90175
Fix aspect-ratio-tool lint
ajlende Jun 19, 2023
677fa10
Fix scale-tool lint
ajlende Jun 19, 2023
6c427f9
Fix width-height-tool lint
ajlende Jun 19, 2023
5995b0f
Fix dimensions-tool lint
ajlende Jun 19, 2023
553aefc
Fix resolution-tool lint
ajlende Jun 19, 2023
b586e13
Add @emption/styled to block-editor
ajlende Jun 19, 2023
838421a
Fix image block lint
ajlende Jun 19, 2023
ddd6568
Update components changelog
ajlende Jun 19, 2023
960f3cc
Fix AspectRatioTool reference
ajlende Jun 19, 2023
3599b94
Support 'auto' in width-height-tool
ajlende Jun 19, 2023
c796ed2
Make null/undefined values mean 'auto' instead of defaultValue in asp…
ajlende Jun 19, 2023
2786075
Add deprecation for image block
ajlende Jun 19, 2023
dcdf401
Fix ResizableBox interactions
ajlende Jun 19, 2023
5daea64
Add comments for default values
ajlende Jun 19, 2023
0725e22
Fix ResizableBox with auto w/h
ajlende Jun 20, 2023
b036600
Clear aspect-ratio on resize
ajlende Jun 20, 2023
d31da45
Add TODO comment for ResolutionTool defaultValue
ajlende Jun 20, 2023
d592833
Move the scale hide/show into dimensions controls
ajlende Jun 20, 2023
38012d4
Add first test
ajlende Jun 20, 2023
c32612e
Fix scale being set after it was deleted
ajlende Jun 20, 2023
725cb49
WIP writing tests
ajlende Jun 20, 2023
27c2e91
Update test
ajlende Jun 20, 2023
ab5089e
UI tweaks
richtabor Jun 20, 2023
8327aba
Move alt text as ToolsPanelItem
richtabor Jun 20, 2023
e1e5c43
Tweak default scale option help text
richtabor Jun 20, 2023
df0be1e
Only use contain and cover for image scale options
richtabor Jun 20, 2023
914bc17
Update test
ajlende Jun 20, 2023
5edf2b4
Test the remaining callback values
ajlende Jun 20, 2023
e9ce885
Add comment about toStrictEqual
ajlende Jun 20, 2023
4137c7b
Add test for setting custom aspect ratio and then resetting
ajlende Jun 20, 2023
9b4712f
Move custom scaleOptions to the image block
ajlende Jun 20, 2023
1687ff7
Remember last aspect ratio so it can be restored when with/height are…
ajlende Jun 21, 2023
e49439b
Remove unused import
ajlende Jun 21, 2023
031beb4
Format code
ajlende Jun 21, 2023
c2a06a5
Remove image w/h reset when a new image is added
ajlende Jun 21, 2023
f28f6c6
Use UnitControl's default units instead of spacing.units
ajlende Jun 21, 2023
3dd4db4
Provide the complete set of object-fit options by default
ajlende Jun 21, 2023
1e68dae
Update TODO that will be committed
ajlende Jun 21, 2023
9d3728b
Clean up evalAspectRatio and add docs
ajlende Jun 21, 2023
a08ea96
Someone can file a bug report if offsetWidth/offsetHeight causes issues
ajlende Jun 21, 2023
eeaa22d
I couldn't figure out why height depended on having a custom border, …
ajlende Jun 21, 2023
8890bf5
Update docs for image block
ajlende Jun 21, 2023
119b59d
Update comment about default value
ajlende Jun 21, 2023
fe0abab
Fix redundant wording
ajlende Jun 21, 2023
236bda2
I think the img width and height attributes can be removed if they're…
ajlende Jun 21, 2023
a80fe1f
Update package-lock.json with @emotion/styled dependency
ajlende Jun 21, 2023
5f41483
Update mock calls for test example
ajlende Jun 21, 2023
cf25eb5
Simplify test values
ajlende Jun 21, 2023
136f139
Consolidate mock calls expect
ajlende Jun 21, 2023
66111d1
Require defaultScale and defaultAspectRatio for DimensionsTool
ajlende Jun 21, 2023
73f3757
Add DimensionsTool tests for all custom transitions
ajlende Jun 21, 2023
6ad1e81
Remove comment about matching aspect ratio options
ajlende Jun 21, 2023
d1800fb
Remove redundant check in tests
ajlende Jun 21, 2023
eb47133
Add comments to defaultAspectRatio and defaultScale
ajlende Jun 22, 2023
26fc552
Organize tests by which field is being updated
ajlende Jun 22, 2023
fc78634
Fix type conversion
ajlende Jun 22, 2023
c723a0d
Add state diagram for last two tests
ajlende Jun 22, 2023
92e9aff
Refactor and fix some tests
ajlende Jun 22, 2023
02dd139
Fix and simplify WidthHeightTool onChange
ajlende Jun 22, 2023
6cbe62d
Remove default scale option in image block.json
ajlende Jun 22, 2023
b5d493a
Simplify DimensionsTool onChange logic
ajlende Jun 22, 2023
140c14c
Update block deprecations with width and height
ajlende Jun 22, 2023
5ca29e7
Revert image block width and height attributes to numbers since we on…
ajlende Jun 22, 2023
db6beb8
Revert "Update block deprecations with width and height"
ajlende Jun 22, 2023
ddc0f87
Prevent NaN width/height
ajlende Jun 22, 2023
a508113
Fix DimensionTool width/height units
ajlende Jun 22, 2023
83c69fc
Fix JSDoc Dimenstions width/height types
ajlende Jun 22, 2023
4b930a8
No default needed for ResolutionTool
ajlende Jun 22, 2023
10b4363
Fix drag handle aspect ratio reset
ajlende Jun 22, 2023
341cd50
Simplify null checks
ajlende Jun 22, 2023
9baf030
Stop using pxWidth and pxHeight
ajlende Jun 22, 2023
e6e7af7
Remove e2e tests that reference the scale button that was removed
ajlende Jun 22, 2023
5cdf2d2
Fix image scaling for small images
ajlende Jun 22, 2023
73569a8
Try fixing aspectRatio only images
ajlende Jun 22, 2023
c127347
Update test to respect the new aspect ratio behavior
jeryj Jun 22, 2023
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
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre
- **Name:** core/image
- **Category:** media
- **Supports:** anchor, behaviors (lightbox), color (~~background~~, ~~text~~), filter (duotone)
- **Attributes:** align, alt, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, sizeSlug, title, url, width
- **Attributes:** align, alt, aspectRatio, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, scale, sizeSlug, title, url, width

## Latest Comments

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/block-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
],
"dependencies": {
"@babel/runtime": "^7.16.0",
"@emotion/styled": "^11.6.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

This results in an unexpected transitive peer dependency on @emotion/react. See #52474 for details.

"@react-spring/web": "^9.4.5",
"@wordpress/a11y": "file:../a11y",
"@wordpress/api-fetch": "file:../api-fetch",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* WordPress dependencies
*/
import {
SelectControl,
__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';

/**
* @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
*/

/**
* @type {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
* @param {string} [value] New aspect ratio value.
* @return {void} No return.
*/

/**
* @typedef {Object} AspectRatioToolProps
* @property {string} [panelId] ID of the panel this tool is associated with.
* @property {string} [value] Current aspect ratio value.
* @property {AspectRatioToolPropsOnChange} [onChange] Callback to update the aspect ratio value.
* @property {SelectControlProps[]} [options] Aspect ratio options.
* @property {string} [defaultValue] Default aspect ratio value.
* @property {boolean} [isShownByDefault] Whether the tool is shown by default.
*/

export default function AspectRatioTool( {
panelId,
value,
onChange = () => {},
options = DEFAULT_ASPECT_RATIO_OPTIONS,
defaultValue = DEFAULT_ASPECT_RATIO_OPTIONS[ 0 ].value,
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';

return (
<ToolsPanelItem
hasValue={ () => displayValue !== defaultValue }
label={ __( 'Aspect ratio' ) }
onDeselect={ () => onChange( undefined ) }
isShownByDefault={ isShownByDefault }
panelId={ panelId }
>
<SelectControl
label={ __( 'Aspect ratio' ) }
value={ displayValue }
options={ options }
onChange={ onChange }
size={ '__unstable-large' }
__nextHasNoMarginBottom
/>
</ToolsPanelItem>
);
}
212 changes: 212 additions & 0 deletions packages/block-editor/src/components/dimensions-tool/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import AspectRatioTool from './aspect-ratio-tool';
import ScaleTool from './scale-tool';
import WidthHeightTool from './width-height-tool';

/**
* @typedef {import('@wordpress/components/build-types/select-control/types').SelectControlProps} SelectControlProps
*/

/**
* @typedef {import('@wordpress/components/build-types/unit-control/types').WPUnitControlUnit} WPUnitControlUnit
*/

/**
* @typedef {Object} Dimensions
* @property {string} [width] CSS width property.
* @property {string} [height] CSS height property.
* @property {string} [scale] CSS object-fit property.
* @property {string} [aspectRatio] CSS aspect-ratio property.
*/

/**
* @callback DimensionsControlsOnChange
* @param {Dimensions} nextValue
* @return {void}
*/

/**
* @typedef {Object} DimensionsControlsProps
* @property {string} [panelId] ID of the panel that contains the controls.
* @property {Dimensions} [value] Current dimensions values.
* @property {DimensionsControlsOnChange} [onChange] Callback to update the dimensions values.
* @property {SelectControlProps[]} [aspectRatioOptions] Aspect ratio options.
* @property {SelectControlProps[]} [scaleOptions] Scale options.
* @property {WPUnitControlUnit[]} [unitsOptions] Units options.
*/

/**
* Component that renders controls to edit the dimensions of an image or container.
*
* @param {DimensionsControlsProps} props The component props.
*
* @return {WPElement} The dimensions controls.
*/
function DimensionsTool( {
panelId,
value = {},
onChange = () => {},
aspectRatioOptions, // Default options handled by AspectRatioTool.
defaultAspectRatio = 'auto', // Match CSS default value for aspect-ratio.
scaleOptions, // Default options handled by ScaleTool.
defaultScale = 'fill', // Match CSS default value for object-fit.
unitsOptions, // Default options handled by UnitControl.
} ) {
// Coerce undefined and CSS default values to be null.
const width =
value.width === undefined || value.width === 'auto'
? null
: value.width;
const height =
value.height === undefined || value.height === 'auto'
? null
: value.height;
const aspectRatio =
value.aspectRatio === undefined || value.aspectRatio === 'auto'
? null
: value.aspectRatio;
const scale =
value.scale === undefined || value.scale === 'fill'
? null
: value.scale;

// Keep track of state internally, so when the value is cleared by means
// other than directly editing that field, it's easier to restore the
// previous value.
const [ lastScale, setLastScale ] = useState( scale );
const [ lastAspectRatio, setLastAspectRatio ] = useState( aspectRatio );

// 'custom' is not a valid value for CSS aspect-ratio, but it is used in the
// dropdown to indicate that setting both the width and height is the same
// as a custom aspect ratio.
const aspectRatioValue = width && height ? 'custom' : lastAspectRatio;

const showScaleControl = aspectRatio || ( width && height );

return (
<>
<AspectRatioTool
panelId={ panelId }
options={ aspectRatioOptions }
defaultValue={ defaultAspectRatio }
value={ aspectRatioValue }
onChange={ ( nextAspectRatio ) => {
const nextValue = { ...value };

// 'auto' is CSS default, so it gets treated as null.
nextAspectRatio =
nextAspectRatio === 'auto' ? null : nextAspectRatio;

setLastAspectRatio( nextAspectRatio );

// Update aspectRatio.
if ( ! nextAspectRatio ) {
delete nextValue.aspectRatio;
} else {
nextValue.aspectRatio = nextAspectRatio;
}

// Auto-update scale.
if ( ! nextAspectRatio ) {
delete nextValue.scale;
} else if ( lastScale ) {
nextValue.scale = lastScale;
} else {
nextValue.scale = defaultScale;
setLastScale( defaultScale );
}

// Auto-update width and height.
if ( nextAspectRatio && width && height ) {
delete nextValue.height;
}

onChange( nextValue );
} }
/>
{ showScaleControl && (
<ScaleTool
panelId={ panelId }
options={ scaleOptions }
defaultValue={ defaultScale }
value={ lastScale }
onChange={ ( nextScale ) => {
const nextValue = { ...value };

// 'fill' is CSS default, so it gets treated as null.
nextScale = nextScale === 'fill' ? null : nextScale;

setLastScale( nextScale );

// Update scale.
if ( ! nextScale ) {
delete nextValue.scale;
} else {
nextValue.scale = nextScale;
}

onChange( nextValue );
} }
/>
) }
<WidthHeightTool
panelId={ panelId }
units={ unitsOptions }
value={ { width, height } }
onChange={ ( { width: nextWidth, height: nextHeight } ) => {
const nextValue = { ...value };

// 'auto' is CSS default, so it gets treated as null.
nextWidth = nextWidth === 'auto' ? null : nextWidth;
nextHeight = nextHeight === 'auto' ? null : nextHeight;

// Update width.
if ( ! nextWidth ) {
delete nextValue.width;
} else {
nextValue.width = nextWidth;
}

// Update height.
if ( ! nextHeight ) {
delete nextValue.height;
} else {
nextValue.height = nextHeight;
}

// Auto-update aspectRatio.
if ( nextWidth && nextHeight ) {
delete nextValue.aspectRatio;
} else if ( lastAspectRatio ) {
nextValue.aspectRatio = lastAspectRatio;
} else {
// No setting defaultAspectRatio here, because
// aspectRatio is optional in this scenario,
// unlike scale.
}

// Auto-update scale.
if ( ! lastAspectRatio && !! nextWidth !== !! nextHeight ) {
delete nextValue.scale;
} else if ( lastScale ) {
nextValue.scale = lastScale;
} else {
nextValue.scale = defaultScale;
setLastScale( defaultScale );
}

onChange( nextValue );
} }
/>
</>
);
}

export default DimensionsTool;
Loading