import * as React from 'react';
const QUALITY = 1;
/**
* Resolve a corner-resize drag into a crop rect, keeping the opposite corner
* (`anchor`) fixed. When `aspectRatio` is set, one dimension is snapped to the
* ratio first (driven by whichever axis the pointer pushed furthest), then the
* position is derived from the anchor — so a locked resize never drifts the
* anchored corner. Pure (no clamping to image bounds; the caller clamps).
*/ const resolveCornerResize = ({ anchorX, anchorY, point, aspectRatio })=>{
let width = Math.abs(point.x - anchorX);
let height = Math.abs(point.y - anchorY);
if (aspectRatio) {
if (width / aspectRatio >= height) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
}
const x = point.x < anchorX ? anchorX - width : anchorX;
const y = point.y < anchorY ? anchorY - height : anchorY;
return {
x,
y,
width,
height
};
};
/**
* Custom crop state + file producer. Replaces a cropperjs-based version: the
* crop rectangle is stored in natural-image pixels (so it stays locked across
* window resizes), and `AssetCropEditor` overlays a div sized by
* (cropNatural × displayRatio). produceFile draws the natural-px crop region
* straight to a canvas.
*/ const useCropImg = ()=>{
const [naturalSize, setNaturalSize] = React.useState({
width: 0,
height: 0
});
const [crop, setCrop] = React.useState({
x: 0,
y: 0,
width: 0,
height: 0
});
const [aspectRatio, setAspectRatioState] = React.useState(null);
const imageRef = React.useRef(null);
/**
* Call from the
onLoad handler. Captures the natural dimensions and
* seeds the crop to the full image (auto-crop area = 1).
*/ const init = React.useCallback((image)=>{
imageRef.current = image;
const next = {
width: image.naturalWidth,
height: image.naturalHeight
};
setNaturalSize(next);
setCrop({
x: 0,
y: 0,
width: next.width,
height: next.height
});
}, []);
const clamp = (value, min, max)=>Math.min(max, Math.max(min, value));
/** Update crop dimensions, clamping to remain inside the image bounds. */ const setCropSize = React.useCallback((next)=>{
setCrop((prev)=>{
const maxW = naturalSize.width - prev.x;
const maxH = naturalSize.height - prev.y;
let width = next.width !== undefined ? clamp(next.width, 1, maxW) : prev.width;
let height = next.height !== undefined ? clamp(next.height, 1, maxH) : prev.height;
if (aspectRatio) {
// When aspect locked, the axis the user is editing wins; the other
// axis follows. If both changed in one call, prefer width.
if (next.width !== undefined) {
height = clamp(width / aspectRatio, 1, maxH);
} else if (next.height !== undefined) {
width = clamp(height * aspectRatio, 1, maxW);
}
}
return {
...prev,
width,
height
};
});
}, [
naturalSize.width,
naturalSize.height,
aspectRatio
]);
/** Update crop origin, clamping so the crop stays inside the image. */ const setCropPosition = React.useCallback((next)=>{
setCrop((prev)=>{
const x = next.x !== undefined ? clamp(next.x, 0, naturalSize.width - prev.width) : prev.x;
const y = next.y !== undefined ? clamp(next.y, 0, naturalSize.height - prev.height) : prev.y;
return {
...prev,
x,
y
};
});
}, [
naturalSize.width,
naturalSize.height
]);
/** Lock or release the crop aspect ratio. Pass `null` to free it. */ const setAspectRatio = React.useCallback((ratio)=>{
setAspectRatioState(ratio);
if (ratio) {
setCrop((prev)=>{
// Bring the current rect into the requested ratio without escaping
// the image bounds.
const maxW = naturalSize.width - prev.x;
const maxH = naturalSize.height - prev.y;
let width = prev.width;
let height = width / ratio;
if (height > maxH) {
height = maxH;
width = height * ratio;
}
if (width > maxW) {
width = maxW;
height = width / ratio;
}
return {
...prev,
width: Math.round(width),
height: Math.round(height)
};
});
}
}, [
naturalSize.width,
naturalSize.height
]);
/**
* Draw the crop region from the loaded image element straight to a canvas
* and produce a File. The image must be CORS-clean (set crossOrigin on the
*
); otherwise canvas.toBlob throws on tainted canvas.
*/ const produceFile = React.useCallback((name, mimeType, lastModifiedDate)=>new Promise((resolve, reject)=>{
const image = imageRef.current;
if (!image) {
reject(new Error('Image not ready: call init() before produceFile().'));
return;
}
const canvas = document.createElement('canvas');
canvas.width = Math.max(1, Math.round(crop.width));
canvas.height = Math.max(1, Math.round(crop.height));
const context = canvas.getContext('2d');
if (!context) {
reject(new Error('Could not get a 2D canvas context to crop the image.'));
return;
}
context.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0, 0, canvas.width, canvas.height);
canvas.toBlob((blob)=>{
if (!blob) {
reject(new Error('Could not export the cropped image to a blob.'));
return;
}
resolve(new File([
blob
], name, {
type: mimeType,
lastModified: lastModifiedDate ? new Date(lastModifiedDate).getTime() : Date.now()
}));
}, mimeType, QUALITY);
}), [
crop.x,
crop.y,
crop.width,
crop.height
]);
return {
init,
crop,
naturalSize,
aspectRatio,
setCropSize,
setCropPosition,
setAspectRatio,
produceFile,
// Round-aware getters for the px inputs.
width: Math.round(crop.width),
height: Math.round(crop.height)
};
};
export { resolveCornerResize, useCropImg };
//# sourceMappingURL=useCropImg.mjs.map