From f961ca78c519bf8cbd3c70d48607d5bc044c53a3 Mon Sep 17 00:00:00 2001 From: Dominic Tobias Date: Thu, 31 Aug 2023 23:41:39 +0100 Subject: [PATCH] Fix 10.1.6 regression when only minWidth or minHeight is set + ensure new min-dimension crop is always in bounds --- package.json | 2 +- src/ReactCrop.tsx | 90 +++++++++++++++++++++-------------------------- src/demo/Demo.tsx | 9 +++-- 3 files changed, 48 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 95bff04..14f1f84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-image-crop", - "version": "10.1.6", + "version": "10.1.7", "description": "A responsive image cropping tool for React", "repository": "https://github.com/DominicTobias/react-image-crop", "type": "module", diff --git a/src/ReactCrop.tsx b/src/ReactCrop.tsx index 951ad68..4e7d8df 100644 --- a/src/ReactCrop.tsx +++ b/src/ReactCrop.tsx @@ -522,14 +522,27 @@ export class ReactCrop extends PureComponent { return nextCrop } - getPointRegion(box: Rectangle): XYOrds { + getPointRegion(box: Rectangle, origOrd: Ords | undefined, minWidth: number, minHeight: number): XYOrds { const { evData } = this const relativeX = evData.clientX - box.x const relativeY = evData.clientY - box.y - const topHalf = relativeY < evData.startCropY - const leftHalf = relativeX < evData.startCropX + let topHalf: boolean + if (minHeight && origOrd) { + // Uses orig ord (never flip when minHeight != 0) + topHalf = origOrd === 'nw' || origOrd === 'n' || origOrd === 'ne' + } else { + topHalf = relativeY < evData.startCropY + } + + let leftHalf: boolean + if (minWidth && origOrd) { + // Uses orig ord (never flip when minWidth != 0) + leftHalf = origOrd === 'nw' || origOrd === 'w' || origOrd === 'sw' + } else { + leftHalf = relativeX < evData.startCropX + } if (leftHalf) { return topHalf ? 'nw' : 'sw' @@ -538,69 +551,43 @@ export class ReactCrop extends PureComponent { } } - resolveMinDimensions(aspect = 0, minWidth = 0, minHeight = 0) { - if (!aspect) { - return [minWidth, minHeight] + resolveMinDimensions(box: Rectangle, aspect: number, minWidth = 0, minHeight = 0) { + let mw = Math.min(minWidth, box.width) + let mh = Math.min(minHeight, box.height) + + if (!aspect || (!mw && !mh)) { + return [mw, mh] } + const longestSide = Math.max(mw, mh) + + // Use the larger side and infer the other if (aspect > 1) { - // Landscape, respect minWidth and infer minHeight - return [minWidth, minWidth / aspect] + return [longestSide, longestSide / aspect] } else { - // Portrait, respect minHeight and infer minWidth - return [minHeight * aspect, minHeight] + return [longestSide * aspect, longestSide] } } resizeCrop() { const { evData } = this const { aspect = 0, maxWidth, maxHeight } = this.props - const [minWidth, minHeight] = this.resolveMinDimensions(aspect, this.props.minWidth, this.props.minHeight) const box = this.getBox() + const [minWidth, minHeight] = this.resolveMinDimensions(box, aspect, this.props.minWidth, this.props.minHeight) let nextCrop = this.makePixelCrop(box) - let area = this.getPointRegion(box) + let area = this.getPointRegion(box, evData.ord, minWidth, minHeight) const ord = evData.ord || area let xDiff = evData.clientX - evData.startClientX let yDiff = evData.clientY - evData.startClientY - // When min dimensions are set, ensure crop isn't flipped (retain origin ord/ - // area) and not dragged when going beyond the other side (Math.min/max) #554 - if (aspect) { - if (minWidth) { - if (['nw', 'sw'].includes(ord)) { - area = ord as XYOrds - xDiff = Math.min(xDiff, -minWidth) - } else if (['ne', 'se'].includes(ord)) { - area = ord as XYOrds - } - } - - if (minHeight) { - if (['nw', 'ne'].includes(ord)) { - area = ord as XYOrds - yDiff = Math.min(yDiff, -minHeight) - } else if (['sw', 'se'].includes(ord)) { - area = ord as XYOrds - } - } - } else { - if (minWidth) { - if (['nw', 'w', 'sw'].includes(ord)) { - area = 'nw' - xDiff = Math.min(xDiff, -minWidth) - } else if (['ne', 'e', 'se'].includes(ord)) { - area = 'ne' - } - } + // When min dimensions are set, ensure crop isn't dragged when going + // beyond the other side #554 + if ((minWidth && ord === 'nw') || ord === 'w' || ord === 'sw') { + xDiff = Math.min(xDiff, -minWidth) + } - if (minHeight) { - if (['nw', 'n', 'ne'].includes(ord)) { - area = 'nw' - yDiff = Math.min(yDiff, -minHeight) - } else if (['sw', 's', 'se'].includes(ord)) { - area = 'sw' - } - } + if ((minHeight && ord === 'nw') || ord === 'n' || ord === 'ne') { + yDiff = Math.min(yDiff, -minHeight) } const tmpCrop: PixelCrop = { @@ -679,6 +666,11 @@ export class ReactCrop extends PureComponent { nextCrop.height = containedCrop.height } + // When drawing a new crop with min dimensions we allow flipping, but + // ensure we don't flip outside the crop area, just ignore those. + nextCrop.x = clamp(nextCrop.x, 0, box.width - nextCrop.width) + nextCrop.y = clamp(nextCrop.y, 0, box.height - nextCrop.height) + return nextCrop } diff --git a/src/demo/Demo.tsx b/src/demo/Demo.tsx index a7b3589..c7da417 100644 --- a/src/demo/Demo.tsx +++ b/src/demo/Demo.tsx @@ -25,6 +25,9 @@ function centerAspectCrop(mediaWidth: number, mediaHeight: number, aspect: numbe ) } +// const defaultAspect = 9 / 16 +const defaultAspect = 16 / 9 + export function Demo() { const [imgSrc, setImgSrc] = useState('') const previewCanvasRef = useRef(null) @@ -33,7 +36,7 @@ export function Demo() { const [completedCrop, setCompletedCrop] = useState() const [scale, setScale] = useState(1) const [rotate, setRotate] = useState(0) - const [aspect, setAspect] = useState(16 / 9) + const [aspect, setAspect] = useState(defaultAspect) function onSelectFile(e: React.ChangeEvent) { if (e.target.files && e.target.files.length > 0) { @@ -66,10 +69,10 @@ export function Demo() { if (aspect) { setAspect(undefined) } else { - setAspect(16 / 9) + setAspect(defaultAspect) if (imgRef.current) { const { width, height } = imgRef.current - setCrop(centerAspectCrop(width, height, 16 / 9)) + setCrop(centerAspectCrop(width, height, defaultAspect)) } } }