diff --git a/packages/block-editor/src/components/image-editor/cropper.js b/packages/block-editor/src/components/image-editor/cropper.js index c6a005551573be..ed0be17a09b154 100644 --- a/packages/block-editor/src/components/image-editor/cropper.js +++ b/packages/block-editor/src/components/image-editor/cropper.js @@ -23,6 +23,7 @@ export default function ImageCropper( { clientWidth, naturalHeight, naturalWidth, + borderProps, } ) { const { isInProgress, @@ -44,10 +45,15 @@ export default function ImageCropper( { return (
div, > a { @@ -63,7 +67,12 @@ figure.wp-block-gallery.has-nested-images { } } - &.is-style-rounded { + &.has-custom-border img { + box-sizing: border-box; + } + + &.is-style-rounded, + &.has-custom-border { > div, > a { flex: 1 1 auto; diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 25492061048214..ba19aaa9d954d7 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -84,9 +84,15 @@ "background": false }, "__experimentalBorder": { + "color": true, "radius": true, + "width": true, + "__experimentalSelector": "img, .wp-block-image__crop-area", + "__experimentalSkipSerialization": true, "__experimentalDefaultControls": { - "radius": true + "color": true, + "radius": true, + "width": true } }, "__experimentalStyle": { diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index 338eac670c5320..9f5aaee4f7d21a 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -31,6 +31,12 @@ const blockAttributes = { source: 'html', selector: 'figcaption', }, + title: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'title', + }, href: { type: 'string', source: 'attribute', @@ -58,6 +64,9 @@ const blockAttributes = { height: { type: 'number', }, + sizeSlug: { + type: 'string', + }, linkDestination: { type: 'string', }, @@ -85,6 +94,83 @@ const blockSupports = { }; const deprecated = [ + // The following deprecation moves existing border radius styles onto the + // inner img element where new border block support styles must be applied. + // It will also add a new `.has-custom-border` class for existing blocks + // with border radii set. This class is required to improve caption position + // and styling when an image within a gallery has a custom border or + // rounded corners. + // + // See: https://github.com/WordPress/gutenberg/pull/31366/ + { + attributes: blockAttributes, + supports: blockSupports, + save( { attributes } ) { + const { + url, + alt, + caption, + align, + href, + rel, + linkClass, + width, + height, + id, + linkTarget, + sizeSlug, + title, + } = attributes; + + const newRel = isEmpty( rel ) ? undefined : rel; + + const classes = classnames( { + [ `align${ align }` ]: align, + [ `size-${ sizeSlug }` ]: sizeSlug, + 'is-resized': width || height, + } ); + + const image = ( + { + ); + + const figure = ( + <> + { href ? ( + + { image } + + ) : ( + image + ) } + { ! RichText.isEmpty( caption ) && ( + + ) } + + ); + + return ( +
+ { figure } +
+ ); + }, + }, { attributes: { ...blockAttributes, diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 3af2bfe7deffa4..7b426be38e6d85 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, has, omit, pick } from 'lodash'; +import { get, has, isEmpty, omit, pick } from 'lodash'; /** * WordPress dependencies @@ -17,6 +17,7 @@ import { MediaPlaceholder, useBlockProps, store as blockEditorStore, + __experimentalUseBorderProps as useBorderProps, } from '@wordpress/block-editor'; import { useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -296,10 +297,14 @@ export function ImageEdit( { /> ); + const borderProps = useBorderProps( attributes ); + const classes = classnames( className, { 'is-transient': temporaryURL, 'is-resized': !! width || !! height, [ `size-${ sizeSlug }` ]: sizeSlug, + 'has-custom-border': + !! borderProps.className || ! isEmpty( borderProps.style ), } ); const blockProps = useBlockProps( { diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index bcbcb771bd1cbf..52a8e47198d362 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -22,10 +22,6 @@ figure.wp-block-image:not(.wp-block) { margin-top: -9px; margin-left: -9px; } - - &:not(.is-style-rounded) > div:not(.components-placeholder) { - border-radius: inherit; - } } // This is necessary for the editor resize handles to accurately work on a non-floated, non-resized, small image. @@ -94,6 +90,15 @@ figure.wp-block-image:not(.wp-block) { position: relative; max-width: 100%; width: 100%; + overflow: hidden; + + // This removes the border from the img within the image cropper so it + // can be applied to the cropper itself. This then allows the image to be + // cropped within the visual border providing more accurate editing and + // smoother UX. + .reactEasyCrop_Container .reactEasyCrop_Image { + border: none; + } } .wp-block-image__crop-icon { diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 52fa307cecac80..1275af2eb2248d 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, map, pick, includes } from 'lodash'; +import { get, filter, isEmpty, map, pick, includes } from 'lodash'; /** * WordPress dependencies @@ -30,6 +30,7 @@ import { __experimentalImageEditor as ImageEditor, __experimentalImageEditingProvider as ImageEditingProvider, __experimentalGetElementClassName, + __experimentalUseBorderProps as useBorderProps, } from '@wordpress/block-editor'; import { useEffect, useMemo, useState, useRef } from '@wordpress/element'; import { __, sprintf, isRTL } from '@wordpress/i18n'; @@ -57,7 +58,19 @@ import { MIN_SIZE, ALLOWED_MEDIA_TYPES } from './constants'; export default function Image( { temporaryURL, - attributes: { + attributes, + setAttributes, + isSelected, + insertBlocksAfter, + onReplace, + onSelectImage, + onSelectURL, + onUploadError, + containerRef, + context, + clientId, +} ) { + const { url = '', alt, caption, @@ -72,18 +85,7 @@ export default function Image( { height, linkTarget, sizeSlug, - }, - setAttributes, - isSelected, - insertBlocksAfter, - onReplace, - onSelectImage, - onSelectURL, - onUploadError, - containerRef, - context, - clientId, -} ) { + } = attributes; const imageRef = useRef(); const captionRef = useRef(); const prevUrl = usePrevious( url ); @@ -186,7 +188,7 @@ export default function Image( { // Get naturalWidth and naturalHeight from image ref, and fall back to loaded natural // width and height. This resolves an issue in Safari where the loaded natural - // witdth and height is otherwise lost when switching between alignments. + // width and height is otherwise lost when switching between alignments. // See: https://github.com/WordPress/gutenberg/pull/37210. const { naturalWidth, naturalHeight } = useMemo( () => { return { @@ -429,6 +431,11 @@ export default function Image( { defaultedAlt = __( 'This image has an empty alt attribute' ); } + const borderProps = useBorderProps( attributes ); + const isRounded = attributes.className?.includes( 'is-style-rounded' ); + const hasCustomBorder = + !! borderProps.className || ! isEmpty( borderProps.style ); + let img = ( // Disable reason: Image itself is not meant to be interactive, but // should direct focus to block. @@ -445,6 +452,8 @@ export default function Image( { } ); } } ref={ imageRef } + className={ borderProps.className } + style={ borderProps.style } /> { temporaryURL && } @@ -466,6 +475,7 @@ export default function Image( { if ( canEditImage && isEditingImage ) { img = ( a, + &.has-custom-border { img { - border-radius: inherit; + box-sizing: border-box; } } @@ -97,6 +97,42 @@ border-radius: 0; } } + + // The following is required to overcome WP Core applying styles that clear + // img borders with a higher specificity than those added by the border + // block support to provide a default border-style of solid when a border + // color or width has been set. + :where(.has-border-color) { + border-style: solid; + } + :where([style*="border-top-color"]) { + border-top-style: solid; + } + :where([style*="border-right-color"]) { + border-right-style: solid; + } + :where([style*="border-bottom-color"]) { + border-bottom-style: solid; + } + :where([style*="border-left-color"]) { + border-left-style: solid; + } + + :where([style*="border-width"]) { + border-style: solid; + } + :where([style*="border-top-width"]) { + border-top-style: solid; + } + :where([style*="border-right-width"]) { + border-right-style: solid; + } + :where([style*="border-bottom-width"]) { + border-bottom-style: solid; + } + :where([style*="border-left-width"]) { + border-left-style: solid; + } } .wp-block-image figure { diff --git a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.html b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.html index 68626f719fbcdb..8cddf1e7d7c04b 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.html @@ -1,3 +1,3 @@ - -
+ +
\ No newline at end of file diff --git a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.json b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.json index c613b3716e8f0b..ae3bd53950a35b 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.json +++ b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.json @@ -10,13 +10,8 @@ "id": 13, "width": 358, "height": 164, - "linkDestination": "none", "sizeSlug": "large", - "style": { - "border": { - "radius": "10px" - } - } + "linkDestination": "none" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.parsed.json b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.parsed.json index 82c2a2ac866b19..249f11175df421 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.parsed.json +++ b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.parsed.json @@ -7,17 +7,12 @@ "width": 358, "height": 164, "sizeSlug": "large", - "linkDestination": "none", - "style": { - "border": { - "radius": "10px" - } - } + "linkDestination": "none" }, "innerBlocks": [], - "innerHTML": "\n
\"\"
\n", + "innerHTML": "\n
\"\"
\n", "innerContent": [ - "\n
\"\"
\n" + "\n
\"\"
\n" ] } ] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.serialized.html index 5037f335ad989e..5ba1eb754e83f6 100644 --- a/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.serialized.html +++ b/test/integration/fixtures/blocks/core__image__deprecated-align-wrapper.serialized.html @@ -1,3 +1,3 @@ - -
+ +
diff --git a/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.html b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.html new file mode 100644 index 00000000000000..f9ca0510e1af36 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.html @@ -0,0 +1,3 @@ + +
+ \ No newline at end of file diff --git a/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.json b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.json new file mode 100644 index 00000000000000..093398e1d54b76 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.json @@ -0,0 +1,20 @@ +[ + { + "name": "core/image", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "id": 246, + "sizeSlug": "large", + "linkDestination": "none", + "style": { + "border": { + "radius": "50px" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.parsed.json b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.parsed.json new file mode 100644 index 00000000000000..4122c71e044696 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/image", + "attrs": { + "id": 246, + "sizeSlug": "large", + "linkDestination": "none", + "style": { + "border": { + "radius": "50px" + } + } + }, + "innerBlocks": [], + "innerHTML": "\n
\"\"
\n", + "innerContent": [ + "\n
\"\"
\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.serialized.html new file mode 100644 index 00000000000000..c91edbc3363f37 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-border-radius-5.serialized.html @@ -0,0 +1,3 @@ + +
+