chore: Change crop to remove locked aspect ratio
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m21s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 22s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m21s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 22s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
This commit is contained in:
@@ -25,11 +25,13 @@ export const CropTool: React.FC<CropToolProps> = ({
|
|||||||
const imageAreaRef = useRef<HTMLDivElement>(null);
|
const imageAreaRef = useRef<HTMLDivElement>(null);
|
||||||
const [imageMaxHeight, setImageMaxHeight] = useState(0);
|
const [imageMaxHeight, setImageMaxHeight] = useState(0);
|
||||||
|
|
||||||
const { cropArea, isDragging, resetCrop, executeCrop, handleDragStart } =
|
const { cropArea, cropDrawn, isDragging, resetCrop, executeCrop, handleDragStart, handleDrawStart } =
|
||||||
useImageCrop({
|
useImageCrop({
|
||||||
aspectRatio: lockAspectRatio ? aspectRatio : undefined,
|
aspectRatio: lockAspectRatio ? aspectRatio : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showCropArea = cropDrawn || (isDragging && cropArea.width > 1 && cropArea.height > 1);
|
||||||
|
|
||||||
// Measure available height for the image so the crop container
|
// Measure available height for the image so the crop container
|
||||||
// matches the rendered image exactly (fixes mobile crop offset)
|
// matches the rendered image exactly (fixes mobile crop offset)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -108,126 +110,150 @@ export const CropTool: React.FC<CropToolProps> = ({
|
|||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Draw surface for free-form rectangle drawing */}
|
||||||
|
{!cropDrawn && (
|
||||||
|
<Box
|
||||||
|
onMouseDown={handleDrawStart}
|
||||||
|
onTouchStart={handleDrawStart}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
cursor: 'crosshair',
|
||||||
|
zIndex: 5,
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Dark overlay outside crop area */}
|
{/* Dark overlay outside crop area */}
|
||||||
<Box
|
{showCropArea && (
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
inset: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Top overlay */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
height: `${cropArea.y}%`,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* Bottom overlay */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
height: `${100 - cropArea.y - cropArea.height}%`,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* Left overlay */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: `${cropArea.y}%`,
|
|
||||||
left: 0,
|
|
||||||
width: `${cropArea.x}%`,
|
|
||||||
height: `${cropArea.height}%`,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* Right overlay */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: `${cropArea.y}%`,
|
|
||||||
right: 0,
|
|
||||||
width: `${100 - cropArea.x - cropArea.width}%`,
|
|
||||||
height: `${cropArea.height}%`,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Crop area with handles */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: `${cropArea.y}%`,
|
|
||||||
left: `${cropArea.x}%`,
|
|
||||||
width: `${cropArea.width}%`,
|
|
||||||
height: `${cropArea.height}%`,
|
|
||||||
border: '2px solid white',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Move handle (center area) */}
|
|
||||||
<CropHandleArea
|
|
||||||
handle="move"
|
|
||||||
onDragStart={handleDragStart}
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
inset: 8,
|
|
||||||
cursor: 'move',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Corner handles */}
|
|
||||||
<CropHandle handle="nw" onDragStart={handleDragStart} position="top-left" />
|
|
||||||
<CropHandle handle="ne" onDragStart={handleDragStart} position="top-right" />
|
|
||||||
<CropHandle handle="sw" onDragStart={handleDragStart} position="bottom-left" />
|
|
||||||
<CropHandle handle="se" onDragStart={handleDragStart} position="bottom-right" />
|
|
||||||
|
|
||||||
{/* Edge handles */}
|
|
||||||
<CropHandle handle="n" onDragStart={handleDragStart} position="top" />
|
|
||||||
<CropHandle handle="s" onDragStart={handleDragStart} position="bottom" />
|
|
||||||
<CropHandle handle="w" onDragStart={handleDragStart} position="left" />
|
|
||||||
<CropHandle handle="e" onDragStart={handleDragStart} position="right" />
|
|
||||||
|
|
||||||
{/* Grid lines for alignment */}
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: '1fr 1fr 1fr',
|
|
||||||
gridTemplateRows: '1fr 1fr 1fr',
|
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
opacity: isDragging ? 1 : 0.5,
|
|
||||||
transition: 'opacity 0.2s',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Array.from({ length: 9 }).map((_, i) => (
|
{/* Top overlay */}
|
||||||
<Box
|
<Box
|
||||||
key={i}
|
sx={{
|
||||||
sx={{
|
position: 'absolute',
|
||||||
borderRight: i % 3 !== 2 ? '1px solid rgba(255,255,255,0.3)' : 'none',
|
top: 0,
|
||||||
borderBottom: i < 6 ? '1px solid rgba(255,255,255,0.3)' : 'none',
|
left: 0,
|
||||||
}}
|
right: 0,
|
||||||
/>
|
height: `${cropArea.y}%`,
|
||||||
))}
|
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Bottom overlay */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: `${100 - cropArea.y - cropArea.height}%`,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Left overlay */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${cropArea.y}%`,
|
||||||
|
left: 0,
|
||||||
|
width: `${cropArea.x}%`,
|
||||||
|
height: `${cropArea.height}%`,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Right overlay */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${cropArea.y}%`,
|
||||||
|
right: 0,
|
||||||
|
width: `${100 - cropArea.x - cropArea.width}%`,
|
||||||
|
height: `${cropArea.height}%`,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
)}
|
||||||
|
|
||||||
|
{/* Crop area border and handles */}
|
||||||
|
{showCropArea && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: `${cropArea.y}%`,
|
||||||
|
left: `${cropArea.x}%`,
|
||||||
|
width: `${cropArea.width}%`,
|
||||||
|
height: `${cropArea.height}%`,
|
||||||
|
border: '2px solid white',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Handles only appear after drawing is complete */}
|
||||||
|
{cropDrawn && (
|
||||||
|
<>
|
||||||
|
{/* Move handle (center area) */}
|
||||||
|
<CropHandleArea
|
||||||
|
handle="move"
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 8,
|
||||||
|
cursor: 'move',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Corner handles */}
|
||||||
|
<CropHandle handle="nw" onDragStart={handleDragStart} position="top-left" />
|
||||||
|
<CropHandle handle="ne" onDragStart={handleDragStart} position="top-right" />
|
||||||
|
<CropHandle handle="sw" onDragStart={handleDragStart} position="bottom-left" />
|
||||||
|
<CropHandle handle="se" onDragStart={handleDragStart} position="bottom-right" />
|
||||||
|
|
||||||
|
{/* Edge handles */}
|
||||||
|
<CropHandle handle="n" onDragStart={handleDragStart} position="top" />
|
||||||
|
<CropHandle handle="s" onDragStart={handleDragStart} position="bottom" />
|
||||||
|
<CropHandle handle="w" onDragStart={handleDragStart} position="left" />
|
||||||
|
<CropHandle handle="e" onDragStart={handleDragStart} position="right" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Grid lines for alignment */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 1fr 1fr',
|
||||||
|
gridTemplateRows: '1fr 1fr 1fr',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
opacity: isDragging ? 1 : 0.5,
|
||||||
|
transition: 'opacity 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 9 }).map((_, i) => (
|
||||||
|
<Box
|
||||||
|
key={i}
|
||||||
|
sx={{
|
||||||
|
borderRight: i % 3 !== 2 ? '1px solid rgba(255,255,255,0.3)' : 'none',
|
||||||
|
borderBottom: i < 6 ? '1px solid rgba(255,255,255,0.3)' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<Box sx={{ px: 2, py: 1, textAlign: 'center' }}>
|
<Box sx={{ px: 2, py: 1, textAlign: 'center' }}>
|
||||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.7)' }}>
|
||||||
Drag to adjust crop area
|
{cropDrawn ? 'Drag handles to adjust crop area' : 'Tap and drag to select crop area'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -255,7 +281,7 @@ export const CropTool: React.FC<CropToolProps> = ({
|
|||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
startIcon={<RefreshIcon />}
|
startIcon={<RefreshIcon />}
|
||||||
sx={{ color: 'white' }}
|
sx={{ color: 'white' }}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing || !cropDrawn}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
@@ -271,7 +297,7 @@ export const CropTool: React.FC<CropToolProps> = ({
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing || !cropDrawn}
|
||||||
aria-label="Confirm crop"
|
aria-label="Confirm crop"
|
||||||
sx={{
|
sx={{
|
||||||
width: 56,
|
width: 56,
|
||||||
|
|||||||
@@ -18,16 +18,20 @@ interface UseImageCropOptions {
|
|||||||
interface UseImageCropReturn {
|
interface UseImageCropReturn {
|
||||||
/** Current crop area */
|
/** Current crop area */
|
||||||
cropArea: CropArea;
|
cropArea: CropArea;
|
||||||
|
/** Whether user has drawn a crop rectangle */
|
||||||
|
cropDrawn: boolean;
|
||||||
/** Whether user is actively dragging */
|
/** Whether user is actively dragging */
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
/** Set crop area */
|
/** Set crop area */
|
||||||
setCropArea: (area: CropArea) => void;
|
setCropArea: (area: CropArea) => void;
|
||||||
/** Reset crop to initial/default */
|
/** Reset crop to drawing mode */
|
||||||
resetCrop: () => void;
|
resetCrop: () => void;
|
||||||
/** Execute crop and return cropped blob */
|
/** Execute crop and return cropped blob */
|
||||||
executeCrop: (imageSrc: string, mimeType?: string) => Promise<Blob>;
|
executeCrop: (imageSrc: string, mimeType?: string) => Promise<Blob>;
|
||||||
/** Handle drag start for crop handles */
|
/** Handle drag start for crop handles */
|
||||||
handleDragStart: (handle: CropHandle, event: React.MouseEvent | React.TouchEvent) => void;
|
handleDragStart: (handle: CropHandle, event: React.MouseEvent | React.TouchEvent) => void;
|
||||||
|
/** Handle draw start for free-form rectangle drawing */
|
||||||
|
handleDrawStart: (event: React.MouseEvent | React.TouchEvent) => void;
|
||||||
/** Handle move during drag */
|
/** Handle move during drag */
|
||||||
handleMove: (event: MouseEvent | TouchEvent) => void;
|
handleMove: (event: MouseEvent | TouchEvent) => void;
|
||||||
/** Handle drag end */
|
/** Handle drag end */
|
||||||
@@ -78,12 +82,22 @@ export function useImageCrop(options: UseImageCropOptions = {}): UseImageCropRet
|
|||||||
const [cropArea, setCropAreaState] = useState<CropArea>(
|
const [cropArea, setCropAreaState] = useState<CropArea>(
|
||||||
getAspectRatioAdjustedCrop(initialCrop)
|
getAspectRatioAdjustedCrop(initialCrop)
|
||||||
);
|
);
|
||||||
|
const [cropDrawn, setCropDrawn] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
const activeHandleRef = useRef<CropHandle | null>(null);
|
const activeHandleRef = useRef<CropHandle | null>(null);
|
||||||
const startPosRef = useRef({ x: 0, y: 0 });
|
const startPosRef = useRef({ x: 0, y: 0 });
|
||||||
const startCropRef = useRef<CropArea>(cropArea);
|
const startCropRef = useRef<CropArea>(cropArea);
|
||||||
const containerRef = useRef<{ width: number; height: number }>({ width: 100, height: 100 });
|
const containerRef = useRef<{ width: number; height: number; left: number; top: number }>({
|
||||||
|
width: 100, height: 100, left: 0, top: 0,
|
||||||
|
});
|
||||||
|
const isDrawingRef = useRef(false);
|
||||||
|
const drawOriginRef = useRef({ x: 0, y: 0 });
|
||||||
|
const cropAreaRef = useRef(cropArea);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cropAreaRef.current = cropArea;
|
||||||
|
}, [cropArea]);
|
||||||
|
|
||||||
const setCropArea = useCallback(
|
const setCropArea = useCallback(
|
||||||
(area: CropArea) => {
|
(area: CropArea) => {
|
||||||
@@ -94,6 +108,7 @@ export function useImageCrop(options: UseImageCropOptions = {}): UseImageCropRet
|
|||||||
|
|
||||||
const resetCrop = useCallback(() => {
|
const resetCrop = useCallback(() => {
|
||||||
setCropAreaState(getAspectRatioAdjustedCrop(initialCrop));
|
setCropAreaState(getAspectRatioAdjustedCrop(initialCrop));
|
||||||
|
setCropDrawn(false);
|
||||||
}, [initialCrop, getAspectRatioAdjustedCrop]);
|
}, [initialCrop, getAspectRatioAdjustedCrop]);
|
||||||
|
|
||||||
const constrainCrop = useCallback(
|
const constrainCrop = useCallback(
|
||||||
@@ -136,19 +151,75 @@ export function useImageCrop(options: UseImageCropOptions = {}): UseImageCropRet
|
|||||||
const container = target.closest('[data-crop-container]');
|
const container = target.closest('[data-crop-container]');
|
||||||
if (container) {
|
if (container) {
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
containerRef.current = { width: rect.width, height: rect.height };
|
containerRef.current = { width: rect.width, height: rect.height, left: rect.left, top: rect.top };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cropArea]
|
[cropArea]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMove = useCallback(
|
const handleDrawStart = useCallback(
|
||||||
(event: MouseEvent | TouchEvent) => {
|
(event: React.MouseEvent | React.TouchEvent) => {
|
||||||
if (!activeHandleRef.current || !isDragging) return;
|
event.preventDefault();
|
||||||
|
|
||||||
|
const target = event.currentTarget as HTMLElement;
|
||||||
|
const container = target.closest('[data-crop-container]');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
containerRef.current = { width: rect.width, height: rect.height, left: rect.left, top: rect.top };
|
||||||
|
|
||||||
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||||
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||||
|
|
||||||
|
const x = Math.max(0, Math.min(100, ((clientX - rect.left) / rect.width) * 100));
|
||||||
|
const y = Math.max(0, Math.min(100, ((clientY - rect.top) / rect.height) * 100));
|
||||||
|
|
||||||
|
startPosRef.current = { x: clientX, y: clientY };
|
||||||
|
drawOriginRef.current = { x, y };
|
||||||
|
|
||||||
|
setCropAreaState({ x, y, width: 0, height: 0 });
|
||||||
|
|
||||||
|
isDrawingRef.current = true;
|
||||||
|
activeHandleRef.current = null;
|
||||||
|
setIsDragging(true);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMove = useCallback(
|
||||||
|
(event: MouseEvent | TouchEvent) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
|
||||||
|
const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
|
||||||
|
|
||||||
|
// Free-form drawing mode: compute rectangle from origin to current pointer
|
||||||
|
if (isDrawingRef.current) {
|
||||||
|
const currentX = Math.max(0, Math.min(100,
|
||||||
|
((clientX - containerRef.current.left) / containerRef.current.width) * 100));
|
||||||
|
const currentY = Math.max(0, Math.min(100,
|
||||||
|
((clientY - containerRef.current.top) / containerRef.current.height) * 100));
|
||||||
|
|
||||||
|
const originX = drawOriginRef.current.x;
|
||||||
|
const originY = drawOriginRef.current.y;
|
||||||
|
|
||||||
|
let newCrop: CropArea = {
|
||||||
|
x: Math.min(originX, currentX),
|
||||||
|
y: Math.min(originY, currentY),
|
||||||
|
width: Math.abs(currentX - originX),
|
||||||
|
height: Math.abs(currentY - originY),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (aspectRatio) {
|
||||||
|
newCrop.height = newCrop.width / aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCropAreaState(newCrop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeHandleRef.current) return;
|
||||||
|
|
||||||
// Calculate delta as percentage of container
|
// Calculate delta as percentage of container
|
||||||
const deltaX = ((clientX - startPosRef.current.x) / containerRef.current.width) * 100;
|
const deltaX = ((clientX - startPosRef.current.x) / containerRef.current.width) * 100;
|
||||||
const deltaY = ((clientY - startPosRef.current.y) / containerRef.current.height) * 100;
|
const deltaY = ((clientY - startPosRef.current.y) / containerRef.current.height) * 100;
|
||||||
@@ -234,13 +305,20 @@ export function useImageCrop(options: UseImageCropOptions = {}): UseImageCropRet
|
|||||||
|
|
||||||
setCropAreaState(constrainCrop(newCrop));
|
setCropAreaState(constrainCrop(newCrop));
|
||||||
},
|
},
|
||||||
[isDragging, constrainCrop]
|
[isDragging, constrainCrop, aspectRatio]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDragEnd = useCallback(() => {
|
const handleDragEnd = useCallback(() => {
|
||||||
|
if (isDrawingRef.current) {
|
||||||
|
isDrawingRef.current = false;
|
||||||
|
const area = cropAreaRef.current;
|
||||||
|
if (area.width >= minSize && area.height >= minSize) {
|
||||||
|
setCropDrawn(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
activeHandleRef.current = null;
|
activeHandleRef.current = null;
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
}, []);
|
}, [minSize]);
|
||||||
|
|
||||||
// Add global event listeners for drag
|
// Add global event listeners for drag
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -320,11 +398,13 @@ export function useImageCrop(options: UseImageCropOptions = {}): UseImageCropRet
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
cropArea,
|
cropArea,
|
||||||
|
cropDrawn,
|
||||||
isDragging,
|
isDragging,
|
||||||
setCropArea,
|
setCropArea,
|
||||||
resetCrop,
|
resetCrop,
|
||||||
executeCrop,
|
executeCrop,
|
||||||
handleDragStart,
|
handleDragStart,
|
||||||
|
handleDrawStart,
|
||||||
handleMove,
|
handleMove,
|
||||||
handleDragEnd,
|
handleDragEnd,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user