From 4fef200a829132e896faeb9a8cc4d320348af056 Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Fri, 16 Oct 2026 15:42:54 -0300 Subject: [PATCH 1/2] feat!: constrain the image to the component's max height BREAKING CHANGE: setImageFullHeight was removed; use getStyle().set("max-height", "100vh") instead. Close #12 Close #8 --- README.md | 6 +++++ .../vaadin/addons/imagecrop/ImageCrop.java | 26 ++++++------------- .../frontend/styles/image-crop-styles.css | 11 ++++++-- .../addons/imagecrop/UploadImageCropDemo.java | 2 ++ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f937b9d..039b529 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,12 @@ ImageCrop imageCrop = new ImageCrop(DownloadHandler.forClassResource(getClass(), add(imageCrop); ``` +* Constrain the size of large images + +```java +imageCrop.getStyle().set("max-height", "70vh"); +``` + * Get cropped image ```java diff --git a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java index 3181f1e..d954cdf 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java +++ b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java @@ -39,7 +39,14 @@ * react-image-crop * library. * This component allows users to define and manipulate crop areas on images. - * + * + *

+ * The image is scaled down, keeping its aspect ratio, to fit the available width + * and the component's {@code max-height}. For instance, + * {@code getStyle().set("max-height", "100vh")} keeps a large image within the + * viewport. Images smaller than these limits are shown at their natural size. + *

+ * * @author Paola De Bartolo / Flowing Code */ @NpmPackage(value = "react-image-crop", version = "11.0.6") @@ -48,8 +55,6 @@ @CssImport("react-image-crop/dist/ReactCrop.css") @CssImport("./styles/image-crop-styles.css") public class ImageCrop extends ReactAdapterComponent { - - private static final String IMG_FULL_HEIGHT_CLASS_NAME = "img-full-height"; private static final String IMG_SRC_RESOURCE_ATTRIBUTE = "img-src"; @@ -497,21 +502,6 @@ public String getCroppedImageDataUri() { return this.croppedImageDataUri; } - /** - * Sets the image to occupy the full viewport height when enabled. - * If {@code fullHeight} is {@code true}, applies a CSS class that - * sets the image height to 100vh. If {@code false}, removes the class - * to revert to the default height. - * - * @param fullHeight whether the image should fill the viewport height - */ - public void setImageFullHeight(Boolean fullHeight) { - if (fullHeight) - this.addClassName(IMG_FULL_HEIGHT_CLASS_NAME); - else - this.removeClassName(IMG_FULL_HEIGHT_CLASS_NAME); - } - /** * Decodes the cropped image data URI and returns it as a byte array. If the image data URI is not * in the format "data:image/*;base64,", it will be decoded assuming it is a Base64 encoded diff --git a/src/main/resources/META-INF/frontend/styles/image-crop-styles.css b/src/main/resources/META-INF/frontend/styles/image-crop-styles.css index 79463c2..8a17004 100644 --- a/src/main/resources/META-INF/frontend/styles/image-crop-styles.css +++ b/src/main/resources/META-INF/frontend/styles/image-crop-styles.css @@ -18,6 +18,13 @@ * #L% */ -.img-full-height img { - height: 100vh; +image-crop { + display: inline-block; + max-width: 100%; +} + +/* Propagate the component's max-height to the image (react-image-crop + applies max-height: inherit to its wrapper and to the image). */ +image-crop .ReactCrop { + max-height: inherit; } diff --git a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java index cb91d8e..c83d1cc 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/imagecrop/UploadImageCropDemo.java @@ -106,6 +106,8 @@ private void openCropDialog(ByteArrayOutputStream outputStream, String mimeType) imageCrop.setCircularCrop(true); imageCrop.setCrop(new Crop("%", 25, 25, 50, 50)); // centered crop imageCrop.setKeepSelection(true); + // keep large images within the dialog + imageCrop.getStyle().set("max-height", "70vh"); cropButton.addClickListener(event -> { newCroppedPicture = imageCrop.getCroppedImageBase64(); From 8895cf0baa6dfc4148c91151486b2dac2b9a244d Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Tue, 20 Oct 2026 16:25:35 -0300 Subject: [PATCH 2/2] WIP: fit the image to the component's measured height --- README.md | 2 ++ .../vaadin/addons/imagecrop/ImageCrop.java | 5 +-- .../META-INF/frontend/src/image-crop.tsx | 35 +++++++++++++++++++ .../frontend/styles/image-crop-styles.css | 10 +++--- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 039b529..13895cf 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ add(imageCrop); * Constrain the size of large images +The image is scaled down to fit the component's size, whether it is set with `height` or `max-height` (in any unit) or given by the layout. + ```java imageCrop.getStyle().set("max-height", "70vh"); ``` diff --git a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java index d954cdf..6b25b40 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java +++ b/src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java @@ -41,8 +41,9 @@ * This component allows users to define and manipulate crop areas on images. * *

- * The image is scaled down, keeping its aspect ratio, to fit the available width - * and the component's {@code max-height}. For instance, + * The image is scaled down, keeping its aspect ratio, to fit the component's width + * and height, whether they are set on the component (a {@code height} or a + * {@code max-height} in any unit) or given by the layout it is in. For instance, * {@code getStyle().set("max-height", "100vh")} keeps a large image within the * viewport. Images smaller than these limits are shown at their natural size. *

diff --git a/src/main/resources/META-INF/frontend/src/image-crop.tsx b/src/main/resources/META-INF/frontend/src/image-crop.tsx index cb484a2..793aa61 100644 --- a/src/main/resources/META-INF/frontend/src/image-crop.tsx +++ b/src/main/resources/META-INF/frontend/src/image-crop.tsx @@ -144,6 +144,40 @@ class ImageCropElement extends ReactAdapterElement { ) : percentCrop; + /** + * Fits the image to the height the component actually gets, however it is + * defined: a height, a max-height in any unit, or the layout. CSS alone can't + * do it, since react-image-crop inherits max-height as written, so a + * percentage would resolve against an auto-height parent and be ignored. + * + * The limit is cleared before measuring, so the component is measured without + * it, and is set in pixels only when the image is taller than the available + * space. Smaller images are never upscaled. + */ + const fitImageHeight = () => { + const cropElement = this.querySelector(".ReactCrop"); + const img = imgRef.current; + if (!cropElement || !img) { + return; + } + cropElement.style.maxHeight = ""; + const style = getComputedStyle(this); + const available = this.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom); + if (img.getBoundingClientRect().height > available + 0.5) { + cropElement.style.maxHeight = `${available}px`; + } + }; + + // Refit when the component or its container is resized. + useEffect(() => { + const observer = new ResizeObserver(() => fitImageHeight()); + observer.observe(this); + if (this.parentElement) { + observer.observe(this.parentElement); + } + return () => observer.disconnect(); + }, []); + /** * Normalizes the configured crop when the image loads. The crop is kept as a * percentage of the image's natural size, so both the on-screen selection and @@ -156,6 +190,7 @@ class ImageCropElement extends ReactAdapterElement { * below). setCrop's Javadoc documents the difference. */ const onImageLoad = () => { + fitImageHeight(); const img = imgRef.current; if (!img || !crop) { return; diff --git a/src/main/resources/META-INF/frontend/styles/image-crop-styles.css b/src/main/resources/META-INF/frontend/styles/image-crop-styles.css index 8a17004..549c8b2 100644 --- a/src/main/resources/META-INF/frontend/styles/image-crop-styles.css +++ b/src/main/resources/META-INF/frontend/styles/image-crop-styles.css @@ -18,13 +18,11 @@ * #L% */ +/* min-width/min-height let the component shrink in flex layouts, so the image + is fitted to the space the layout gives it (see fitImageHeight). */ image-crop { display: inline-block; max-width: 100%; -} - -/* Propagate the component's max-height to the image (react-image-crop - applies max-height: inherit to its wrapper and to the image). */ -image-crop .ReactCrop { - max-height: inherit; + min-width: 0; + min-height: 0; }