react-image-crop
Version:
A responsive image cropping tool for React
460 lines (459 loc) • 21.3 kB
JavaScript
var _ = Object.defineProperty;
var $ = (a, h, e) => h in a ? _(a, h, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[h] = e;
var m = (a, h, e) => $(a, typeof h != "symbol" ? h + "" : h, e);
import u, { PureComponent as K, createRef as P } from "react";
const E = {
x: 0,
y: 0,
width: 0,
height: 0,
unit: "px"
}, b = (a, h, e) => Math.min(Math.max(a, h), e), H = (...a) => a.filter((h) => h && typeof h == "string").join(" "), X = (a, h) => a === h || a.width === h.width && a.height === h.height && a.x === h.x && a.y === h.y && a.unit === h.unit;
function B(a, h, e, n) {
const t = D(a, e, n);
return a.width && (t.height = t.width / h), a.height && (t.width = t.height * h), t.y + t.height > n && (t.height = n - t.y, t.width = t.height * h), t.x + t.width > e && (t.width = e - t.x, t.height = t.width / h), a.unit === "%" ? v(t, e, n) : t;
}
function L(a, h, e) {
const n = D(a, h, e);
return n.x = (h - n.width) / 2, n.y = (e - n.height) / 2, a.unit === "%" ? v(n, h, e) : n;
}
function v(a, h, e) {
return a.unit === "%" ? { ...E, ...a, unit: "%" } : {
unit: "%",
x: a.x ? a.x / h * 100 : 0,
y: a.y ? a.y / e * 100 : 0,
width: a.width ? a.width / h * 100 : 0,
height: a.height ? a.height / e * 100 : 0
};
}
function D(a, h, e) {
return a.unit ? a.unit === "px" ? { ...E, ...a, unit: "px" } : {
unit: "px",
x: a.x ? a.x * h / 100 : 0,
y: a.y ? a.y * e / 100 : 0,
width: a.width ? a.width * h / 100 : 0,
height: a.height ? a.height * e / 100 : 0
} : { ...E, ...a, unit: "px" };
}
function k(a, h, e, n, t, d = 0, r = 0, o = n, w = t) {
const i = { ...a };
let s = Math.min(d, n), c = Math.min(r, t), g = Math.min(o, n), p = Math.min(w, t);
h && (h > 1 ? (s = r ? r * h : s, c = s / h, g = o * h) : (c = d ? d / h : c, s = c * h, p = w / h)), i.y < 0 && (i.height = Math.max(i.height + i.y, c), i.y = 0), i.x < 0 && (i.width = Math.max(i.width + i.x, s), i.x = 0);
const l = n - (i.x + i.width);
l < 0 && (i.x = Math.min(i.x, n - s), i.width += l);
const C = t - (i.y + i.height);
if (C < 0 && (i.y = Math.min(i.y, t - c), i.height += C), i.width < s && ((e === "sw" || e == "nw") && (i.x -= s - i.width), i.width = s), i.height < c && ((e === "nw" || e == "ne") && (i.y -= c - i.height), i.height = c), i.width > g && ((e === "sw" || e == "nw") && (i.x -= g - i.width), i.width = g), i.height > p && ((e === "nw" || e == "ne") && (i.y -= p - i.height), i.height = p), h) {
const y = i.width / i.height;
if (y < h) {
const f = Math.max(i.width / h, c);
(e === "nw" || e == "ne") && (i.y -= f - i.height), i.height = f;
} else if (y > h) {
const f = Math.max(i.height * h, s);
(e === "sw" || e == "nw") && (i.x -= f - i.width), i.width = f;
}
}
return i;
}
function I(a, h, e, n) {
const t = { ...a };
return h === "ArrowLeft" ? n === "nw" ? (t.x -= e, t.y -= e, t.width += e, t.height += e) : n === "w" ? (t.x -= e, t.width += e) : n === "sw" ? (t.x -= e, t.width += e, t.height += e) : n === "ne" ? (t.y += e, t.width -= e, t.height -= e) : n === "e" ? t.width -= e : n === "se" && (t.width -= e, t.height -= e) : h === "ArrowRight" && (n === "nw" ? (t.x += e, t.y += e, t.width -= e, t.height -= e) : n === "w" ? (t.x += e, t.width -= e) : n === "sw" ? (t.x += e, t.width -= e, t.height -= e) : n === "ne" ? (t.y -= e, t.width += e, t.height += e) : n === "e" ? t.width += e : n === "se" && (t.width += e, t.height += e)), h === "ArrowUp" ? n === "nw" ? (t.x -= e, t.y -= e, t.width += e, t.height += e) : n === "n" ? (t.y -= e, t.height += e) : n === "ne" ? (t.y -= e, t.width += e, t.height += e) : n === "sw" ? (t.x += e, t.width -= e, t.height -= e) : n === "s" ? t.height -= e : n === "se" && (t.width -= e, t.height -= e) : h === "ArrowDown" && (n === "nw" ? (t.x += e, t.y += e, t.width -= e, t.height -= e) : n === "n" ? (t.y += e, t.height -= e) : n === "ne" ? (t.y += e, t.width -= e, t.height -= e) : n === "sw" ? (t.x -= e, t.width += e, t.height += e) : n === "s" ? t.height += e : n === "se" && (t.width += e, t.height += e)), t;
}
const M = { capture: !0, passive: !1 };
let N = 0;
const x = class x extends K {
constructor() {
super(...arguments);
m(this, "docMoveBound", !1);
m(this, "mouseDownOnCrop", !1);
m(this, "dragStarted", !1);
m(this, "evData", {
startClientX: 0,
startClientY: 0,
startCropX: 0,
startCropY: 0,
clientX: 0,
clientY: 0,
isResize: !0
});
m(this, "componentRef", P());
m(this, "mediaRef", P());
m(this, "resizeObserver");
m(this, "initChangeCalled", !1);
m(this, "instanceId", `rc-${N++}`);
m(this, "state", {
cropIsActive: !1,
newCropIsBeingDrawn: !1
});
m(this, "onCropPointerDown", (e) => {
const { crop: n, disabled: t } = this.props, d = this.getBox();
if (!n)
return;
const r = D(n, d.width, d.height);
if (t)
return;
e.cancelable && e.preventDefault(), this.bindDocMove(), this.componentRef.current.focus({ preventScroll: !0 });
const o = e.target.dataset.ord, w = !!o;
let i = e.clientX, s = e.clientY, c = r.x, g = r.y;
if (o) {
const p = e.clientX - d.x, l = e.clientY - d.y;
let C = 0, y = 0;
o === "ne" || o == "e" ? (C = p - (r.x + r.width), y = l - r.y, c = r.x, g = r.y + r.height) : o === "se" || o === "s" ? (C = p - (r.x + r.width), y = l - (r.y + r.height), c = r.x, g = r.y) : o === "sw" || o == "w" ? (C = p - r.x, y = l - (r.y + r.height), c = r.x + r.width, g = r.y) : (o === "nw" || o == "n") && (C = p - r.x, y = l - r.y, c = r.x + r.width, g = r.y + r.height), i = c + d.x + C, s = g + d.y + y;
}
this.evData = {
startClientX: i,
startClientY: s,
startCropX: c,
startCropY: g,
clientX: e.clientX,
clientY: e.clientY,
isResize: w,
ord: o
}, this.mouseDownOnCrop = !0, this.setState({ cropIsActive: !0 });
});
m(this, "onComponentPointerDown", (e) => {
const { crop: n, disabled: t, locked: d, keepSelection: r, onChange: o } = this.props, w = this.getBox();
if (t || d || r && n)
return;
e.cancelable && e.preventDefault(), this.bindDocMove(), this.componentRef.current.focus({ preventScroll: !0 });
const i = e.clientX - w.x, s = e.clientY - w.y, c = {
unit: "px",
x: i,
y: s,
width: 0,
height: 0
};
this.evData = {
startClientX: e.clientX,
startClientY: e.clientY,
startCropX: i,
startCropY: s,
clientX: e.clientX,
clientY: e.clientY,
isResize: !0
}, this.mouseDownOnCrop = !0, o(D(c, w.width, w.height), v(c, w.width, w.height)), this.setState({ cropIsActive: !0, newCropIsBeingDrawn: !0 });
});
m(this, "onDocPointerMove", (e) => {
const { crop: n, disabled: t, onChange: d, onDragStart: r } = this.props, o = this.getBox();
if (t || !n || !this.mouseDownOnCrop)
return;
e.cancelable && e.preventDefault(), this.dragStarted || (this.dragStarted = !0, r && r(e));
const { evData: w } = this;
w.clientX = e.clientX, w.clientY = e.clientY;
let i;
w.isResize ? i = this.resizeCrop() : i = this.dragCrop(), X(n, i) || d(
D(i, o.width, o.height),
v(i, o.width, o.height)
);
});
m(this, "onComponentKeyDown", (e) => {
const { crop: n, disabled: t, onChange: d, onComplete: r } = this.props;
if (t)
return;
const o = e.key;
let w = !1;
if (!n)
return;
const i = this.getBox(), s = this.makePixelCrop(i), g = (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ? x.nudgeStepLarge : e.shiftKey ? x.nudgeStepMedium : x.nudgeStep;
if (o === "ArrowLeft" ? (s.x -= g, w = !0) : o === "ArrowRight" ? (s.x += g, w = !0) : o === "ArrowUp" ? (s.y -= g, w = !0) : o === "ArrowDown" && (s.y += g, w = !0), w) {
e.cancelable && e.preventDefault(), s.x = b(s.x, 0, i.width - s.width), s.y = b(s.y, 0, i.height - s.height);
const p = D(s, i.width, i.height), l = v(s, i.width, i.height);
d(p, l), r && r(p, l);
}
});
m(this, "onHandlerKeyDown", (e, n) => {
const {
aspect: t = 0,
crop: d,
disabled: r,
minWidth: o = 0,
minHeight: w = 0,
maxWidth: i,
maxHeight: s,
onChange: c,
onComplete: g
} = this.props, p = this.getBox();
if (r || !d)
return;
if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight")
e.stopPropagation(), e.preventDefault();
else
return;
const C = (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) ? x.nudgeStepLarge : e.shiftKey ? x.nudgeStepMedium : x.nudgeStep, y = D(d, p.width, p.height), f = I(y, e.key, C, n), R = k(
f,
t,
n,
p.width,
p.height,
o,
w,
i,
s
);
if (!X(d, R)) {
const Y = v(R, p.width, p.height);
c(R, Y), g && g(R, Y);
}
});
m(this, "onDocPointerDone", (e) => {
const { crop: n, disabled: t, onComplete: d, onDragEnd: r } = this.props, o = this.getBox();
this.unbindDocMove(), !(t || !n) && this.mouseDownOnCrop && (this.mouseDownOnCrop = !1, this.dragStarted = !1, r && r(e), d && d(D(n, o.width, o.height), v(n, o.width, o.height)), this.setState({ cropIsActive: !1, newCropIsBeingDrawn: !1 }));
});
m(this, "onDragFocus", () => {
var e;
(e = this.componentRef.current) == null || e.scrollTo(0, 0);
});
}
get document() {
return document;
}
// We unfortunately get the bounding box every time as x+y changes
// due to scrolling.
getBox() {
const e = this.mediaRef.current;
if (!e)
return { x: 0, y: 0, width: 0, height: 0 };
const { x: n, y: t, width: d, height: r } = e.getBoundingClientRect();
return { x: n, y: t, width: d, height: r };
}
componentDidUpdate(e) {
const { crop: n, onComplete: t } = this.props;
if (t && !e.crop && n) {
const { width: d, height: r } = this.getBox();
d && r && t(D(n, d, r), v(n, d, r));
}
}
componentWillUnmount() {
this.resizeObserver && this.resizeObserver.disconnect(), this.unbindDocMove();
}
bindDocMove() {
this.docMoveBound || (this.document.addEventListener("pointermove", this.onDocPointerMove, M), this.document.addEventListener("pointerup", this.onDocPointerDone, M), this.document.addEventListener("pointercancel", this.onDocPointerDone, M), this.docMoveBound = !0);
}
unbindDocMove() {
this.docMoveBound && (this.document.removeEventListener("pointermove", this.onDocPointerMove, M), this.document.removeEventListener("pointerup", this.onDocPointerDone, M), this.document.removeEventListener("pointercancel", this.onDocPointerDone, M), this.docMoveBound = !1);
}
getCropStyle() {
const { crop: e } = this.props;
if (e)
return {
top: `${e.y}${e.unit}`,
left: `${e.x}${e.unit}`,
width: `${e.width}${e.unit}`,
height: `${e.height}${e.unit}`
};
}
dragCrop() {
const { evData: e } = this, n = this.getBox(), t = this.makePixelCrop(n), d = e.clientX - e.startClientX, r = e.clientY - e.startClientY;
return t.x = b(e.startCropX + d, 0, n.width - t.width), t.y = b(e.startCropY + r, 0, n.height - t.height), t;
}
getPointRegion(e, n, t, d) {
const { evData: r } = this, o = r.clientX - e.x, w = r.clientY - e.y;
let i;
d && n ? i = n === "nw" || n === "n" || n === "ne" : i = w < r.startCropY;
let s;
return t && n ? s = n === "nw" || n === "w" || n === "sw" : s = o < r.startCropX, s ? i ? "nw" : "sw" : i ? "ne" : "se";
}
resolveMinDimensions(e, n, t = 0, d = 0) {
const r = Math.min(t, e.width), o = Math.min(d, e.height);
return !n || !r && !o ? [r, o] : n > 1 ? r ? [r, r / n] : [o * n, o] : o ? [o * n, o] : [r, r / n];
}
resizeCrop() {
const { evData: e } = this, { aspect: n = 0, maxWidth: t, maxHeight: d } = this.props, r = this.getBox(), [o, w] = this.resolveMinDimensions(r, n, this.props.minWidth, this.props.minHeight);
let i = this.makePixelCrop(r);
const s = this.getPointRegion(r, e.ord, o, w), c = e.ord || s;
let g = e.clientX - e.startClientX, p = e.clientY - e.startClientY;
(o && c === "nw" || c === "w" || c === "sw") && (g = Math.min(g, -o)), (w && c === "nw" || c === "n" || c === "ne") && (p = Math.min(p, -w));
const l = {
unit: "px",
x: 0,
y: 0,
width: 0,
height: 0
};
s === "ne" ? (l.x = e.startCropX, l.width = g, n ? (l.height = l.width / n, l.y = e.startCropY - l.height) : (l.height = Math.abs(p), l.y = e.startCropY - l.height)) : s === "se" ? (l.x = e.startCropX, l.y = e.startCropY, l.width = g, n ? l.height = l.width / n : l.height = p) : s === "sw" ? (l.x = e.startCropX + g, l.y = e.startCropY, l.width = Math.abs(g), n ? l.height = l.width / n : l.height = p) : s === "nw" && (l.x = e.startCropX + g, l.width = Math.abs(g), n ? (l.height = l.width / n, l.y = e.startCropY - l.height) : (l.height = Math.abs(p), l.y = e.startCropY + p));
const C = k(
l,
n,
s,
r.width,
r.height,
o,
w,
t,
d
);
return n || x.xyOrds.indexOf(c) > -1 ? i = C : x.xOrds.indexOf(c) > -1 ? (i.x = C.x, i.width = C.width) : x.yOrds.indexOf(c) > -1 && (i.y = C.y, i.height = C.height), i.x = b(i.x, 0, r.width - i.width), i.y = b(i.y, 0, r.height - i.height), i;
}
renderCropSelection() {
const {
ariaLabels: e = x.defaultProps.ariaLabels,
disabled: n,
locked: t,
renderSelectionAddon: d,
ruleOfThirds: r,
crop: o
} = this.props, w = this.getCropStyle();
if (o)
return /* @__PURE__ */ u.createElement(
"div",
{
style: w,
className: "ReactCrop__crop-selection",
onPointerDown: this.onCropPointerDown,
"aria-label": e.cropArea,
tabIndex: 0,
onKeyDown: this.onComponentKeyDown,
role: "group"
},
!n && !t && /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__drag-elements", onFocus: this.onDragFocus }, /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__drag-bar ord-n", "data-ord": "n" }), /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__drag-bar ord-e", "data-ord": "e" }), /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__drag-bar ord-s", "data-ord": "s" }), /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__drag-bar ord-w", "data-ord": "w" }), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-nw",
"data-ord": "nw",
tabIndex: 0,
"aria-label": e.nwDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "nw"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-n",
"data-ord": "n",
tabIndex: 0,
"aria-label": e.nDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "n"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-ne",
"data-ord": "ne",
tabIndex: 0,
"aria-label": e.neDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "ne"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-e",
"data-ord": "e",
tabIndex: 0,
"aria-label": e.eDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "e"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-se",
"data-ord": "se",
tabIndex: 0,
"aria-label": e.seDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "se"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-s",
"data-ord": "s",
tabIndex: 0,
"aria-label": e.sDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "s"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-sw",
"data-ord": "sw",
tabIndex: 0,
"aria-label": e.swDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "sw"),
role: "button"
}
), /* @__PURE__ */ u.createElement(
"div",
{
className: "ReactCrop__drag-handle ord-w",
"data-ord": "w",
tabIndex: 0,
"aria-label": e.wDragHandle,
onKeyDown: (i) => this.onHandlerKeyDown(i, "w"),
role: "button"
}
)),
d && /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__selection-addon", onPointerDown: (i) => i.stopPropagation() }, d(this.state)),
r && /* @__PURE__ */ u.createElement(u.Fragment, null, /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__rule-of-thirds-hz" }), /* @__PURE__ */ u.createElement("div", { className: "ReactCrop__rule-of-thirds-vt" }))
);
}
makePixelCrop(e) {
const n = { ...E, ...this.props.crop || {} };
return D(n, e.width, e.height);
}
render() {
const { aspect: e, children: n, circularCrop: t, className: d, crop: r, disabled: o, locked: w, style: i, ruleOfThirds: s } = this.props, { cropIsActive: c, newCropIsBeingDrawn: g } = this.state, p = r ? this.renderCropSelection() : null, l = H(
"ReactCrop",
d,
c && "ReactCrop--active",
o && "ReactCrop--disabled",
w && "ReactCrop--locked",
g && "ReactCrop--new-crop",
r && e && "ReactCrop--fixed-aspect",
r && t && "ReactCrop--circular-crop",
r && s && "ReactCrop--rule-of-thirds",
!this.dragStarted && r && !r.width && !r.height && "ReactCrop--invisible-crop",
t && "ReactCrop--no-animate"
);
return /* @__PURE__ */ u.createElement("div", { ref: this.componentRef, className: l, style: i }, /* @__PURE__ */ u.createElement("div", { ref: this.mediaRef, className: "ReactCrop__child-wrapper", onPointerDown: this.onComponentPointerDown }, n), r ? /* @__PURE__ */ u.createElement("svg", { className: "ReactCrop__crop-mask", width: "100%", height: "100%" }, /* @__PURE__ */ u.createElement("defs", null, /* @__PURE__ */ u.createElement("mask", { id: `hole-${this.instanceId}` }, /* @__PURE__ */ u.createElement("rect", { width: "100%", height: "100%", fill: "white" }), t ? /* @__PURE__ */ u.createElement(
"ellipse",
{
cx: `${r.x + r.width / 2}${r.unit}`,
cy: `${r.y + r.height / 2}${r.unit}`,
rx: `${r.width / 2}${r.unit}`,
ry: `${r.height / 2}${r.unit}`,
fill: "black"
}
) : /* @__PURE__ */ u.createElement(
"rect",
{
x: `${r.x}${r.unit}`,
y: `${r.y}${r.unit}`,
width: `${r.width}${r.unit}`,
height: `${r.height}${r.unit}`,
fill: "black"
}
))), /* @__PURE__ */ u.createElement("rect", { fill: "black", fillOpacity: 0.5, width: "100%", height: "100%", mask: `url(#hole-${this.instanceId})` })) : void 0, p);
}
};
m(x, "xOrds", ["e", "w"]), m(x, "yOrds", ["n", "s"]), m(x, "xyOrds", ["nw", "ne", "se", "sw"]), m(x, "nudgeStep", 1), m(x, "nudgeStepMedium", 10), m(x, "nudgeStepLarge", 100), m(x, "defaultProps", {
ariaLabels: {
cropArea: "Use the arrow keys to move the crop selection area",
nwDragHandle: "Use the arrow keys to move the north west drag handle to change the crop selection area",
nDragHandle: "Use the up and down arrow keys to move the north drag handle to change the crop selection area",
neDragHandle: "Use the arrow keys to move the north east drag handle to change the crop selection area",
eDragHandle: "Use the up and down arrow keys to move the east drag handle to change the crop selection area",
seDragHandle: "Use the arrow keys to move the south east drag handle to change the crop selection area",
sDragHandle: "Use the up and down arrow keys to move the south drag handle to change the crop selection area",
swDragHandle: "Use the arrow keys to move the south west drag handle to change the crop selection area",
wDragHandle: "Use the up and down arrow keys to move the west drag handle to change the crop selection area"
}
});
let S = x;
export {
S as Component,
S as ReactCrop,
X as areCropsEqual,
L as centerCrop,
b as clamp,
H as cls,
k as containCrop,
v as convertToPercentCrop,
D as convertToPixelCrop,
S as default,
E as defaultCrop,
B as makeAspectCrop,
I as nudgeCrop
};