From ecbd668d8b4e2cdf81a6b3ff3ce4c2b5a19a9794 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 27 Aug 2024 16:21:15 +0800 Subject: [PATCH] Fix resizing to max width in classic themes --- packages/block-library/src/image/edit.js | 24 +++++--- packages/block-library/src/image/editor.scss | 5 ++ packages/block-library/src/image/image.js | 9 +-- .../src/image/use-max-width-observer.js | 57 +++++++++++++++++++ 4 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 packages/block-library/src/image/use-max-width-observer.js diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index ae8461a6b1cf4..d1dc48c5bdda9 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -23,7 +23,6 @@ import { useEffect, useRef, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { image as icon, plugins as pluginsIcon } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; -import { useResizeObserver } from '@wordpress/compose'; /** * Internal dependencies @@ -32,6 +31,7 @@ import { unlock } from '../lock-unlock'; import { useUploadMediaFromBlobURL } from '../utils/hooks'; import Image from './image'; import { isValidFileType } from './utils'; +import { useMaxWidthObserver } from './use-max-width-observer'; /** * Module constants @@ -110,10 +110,15 @@ export function ImageEdit( { metadata, } = attributes; const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); - const figureRef = useRef(); - const [ contentResizeListener, { width: containerWidth } ] = - useResizeObserver(); + const containerRef = useRef(); + // Only observe the max width from the parent container when the parent layout is default or constrained. + // This won't work for flex or grid layouts because the container width changes with image. + // TODO: Find a way to observe the container width for flex and grid layouts. + const isMaxWidthContainerWidth = + parentLayout?.type === 'default' || + parentLayout?.type === 'constrained'; + const [ maxWidthObserver, maxContentWidth ] = useMaxWidthObserver(); const altRef = useRef(); useEffect( () => { @@ -160,7 +165,7 @@ export function ImageEdit( { } function onSelectImagesList( images ) { - const win = figureRef.current?.ownerDocument.defaultView; + const win = containerRef.current?.ownerDocument.defaultView; if ( images.every( ( file ) => file instanceof win.File ) ) { /** @type {File[]} */ @@ -348,7 +353,10 @@ export function ImageEdit( { Object.keys( borderProps.style ).length > 0 ), } ); - const blockProps = useBlockProps( { ref: figureRef, className: classes } ); + const blockProps = useBlockProps( { + ref: containerRef, + className: classes, + } ); // Much of this description is duplicated from MediaPlaceholder. const { lockUrlControls = false, lockUrlControlsMessage } = useSelect( @@ -436,7 +444,7 @@ export function ImageEdit( { clientId={ clientId } blockEditingMode={ blockEditingMode } parentLayoutType={ parentLayout?.type } - containerWidth={ containerWidth } + maxContentWidth={ maxContentWidth } /> } @@ -455,7 +463,7 @@ export function ImageEdit( { { // The listener cannot be placed as the first element as it will break the in-between inserter. // See https://github.com/WordPress/gutenberg/blob/71134165868298fc15e22896d0c28b41b3755ff7/packages/block-editor/src/components/block-list/use-in-between-inserter.js#L120 - contentResizeListener + isSingleSelected && isMaxWidthContainerWidth && maxWidthObserver } ); diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 636741c7d9ddb..e279ca75314c0 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -149,6 +149,11 @@ figure.wp-block-image:not(.wp-block) { text-align: center; } +// Relatively position the alignment container to support the content resizer. +.wp-block[data-align]:has(> .wp-block-image) { + position: relative; +} + .wp-block-image__crop-area { position: relative; max-width: 100%; diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 2de316f78ba5d..eb8b0b9317547 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -108,7 +108,7 @@ export default function Image( { clientId, blockEditingMode, parentLayoutType, - containerWidth, + maxContentWidth, } ) { const { url = '', @@ -934,7 +934,7 @@ export default function Image( { // @todo It would be good to revisit this once a content-width variable // becomes available. const maxWidthBuffer = maxWidth * 2.5; - const maxContentWidth = containerWidth || maxWidthBuffer; + const maxResizeWidth = maxContentWidth || maxWidthBuffer; let showRightHandle = false; let showLeftHandle = false; @@ -980,9 +980,9 @@ export default function Image( { } } showHandle={ isSingleSelected } minWidth={ minWidth } - maxWidth={ maxContentWidth } + maxWidth={ maxResizeWidth } minHeight={ minHeight } - maxHeight={ maxContentWidth / ratio } + maxHeight={ maxResizeWidth / ratio } lockAspectRatio={ ratio } enable={ { top: false, @@ -996,6 +996,7 @@ export default function Image( { // Clear hardcoded width if the resized width is close to the max-content width. if ( + maxContentWidth && // Only do this if the image is bigger than the container to prevent it from being squished. // TODO: Remove this check if the image support setting 100% width. naturalWidth >= maxContentWidth && diff --git a/packages/block-library/src/image/use-max-width-observer.js b/packages/block-library/src/image/use-max-width-observer.js new file mode 100644 index 0000000000000..cb1bdc4e5fdbd --- /dev/null +++ b/packages/block-library/src/image/use-max-width-observer.js @@ -0,0 +1,57 @@ +/** + * WordPress dependencies + */ +import { useRef, useMemo } from '@wordpress/element'; +import { useResizeObserver } from '@wordpress/compose'; + +function useMaxWidthObserver() { + const [ contentResizeListener, { width } ] = useResizeObserver(); + const observerRef = useRef(); + + const maxWidthObserver = ( + + ); + + const maxWidth = useMemo( () => { + const observer = observerRef.current; + if ( ! observer ) { + return width; + } + + const win = observer.ownerDocument.defaultView; + const observerStyle = win.getComputedStyle( observer ); + const parentStyle = win.getComputedStyle( observer.parentElement ); + + const isParentBorderBox = parentStyle.boxSizing === 'border-box'; + const paddingInline = isParentBorderBox + ? 0 + : parseFloat( parentStyle.paddingLeft ) + + parseFloat( parentStyle.paddingRight ); + + const observerMaxWidth = parseFloat( observerStyle.maxWidth ); + const contentWidth = + width - ( Number.isNaN( paddingInline ) ? 0 : paddingInline ); + + return Number.isNaN( observerMaxWidth ) + ? contentWidth + : Math.min( contentWidth, observerMaxWidth ); + }, [ width ] ); + + return [ maxWidthObserver, maxWidth ]; +} + +export { useMaxWidthObserver };