UNPKG

react-dnd-crop

Version:
1,030 lines (867 loc) 27.6 kB
import React, { PureComponent } from "react"; import PropTypes from "prop-types"; import clsx from "clsx"; // Feature detection // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners let passiveSupported = false; try { window.addEventListener( "test", null, Object.defineProperty({}, "passive", { get: () => { passiveSupported = true; return true; } }) ); } catch (err) {} // eslint-disable-line no-empty function getClientPos(e) { let pageX; let pageY; if (e.touches) { [{ pageX, pageY }] = e.touches; } else { ({ pageX, pageY } = e); } return { x: pageX, y: pageY }; } function clamp(num, min, max) { return Math.min(Math.max(num, min), max); } function isCropValid(crop) { return ( crop && crop.width && crop.height && !isNaN(crop.width) && !isNaN(crop.height) ); } function inverseOrd(ord) { if (ord === "n") return "s"; if (ord === "ne") return "sw"; if (ord === "e") return "w"; if (ord === "se") return "nw"; if (ord === "s") return "n"; if (ord === "sw") return "ne"; if (ord === "w") return "e"; if (ord === "nw") return "se"; return ord; } function makeAspectCrop(crop, imageWidth, imageHeight) { if (isNaN(crop.aspect)) { console.warn( "`crop.aspect` should be a number in order to make an aspect crop", crop ); return crop; } const completeCrop = { unit: "px", x: 0, y: 0, ...crop }; if (crop.width) { completeCrop.height = completeCrop.width / crop.aspect; } if (crop.height) { completeCrop.width = completeCrop.height * crop.aspect; } if (completeCrop.y + completeCrop.height > imageHeight) { completeCrop.height = imageHeight - completeCrop.y; completeCrop.width = completeCrop.height * crop.aspect; } if (completeCrop.x + completeCrop.width > imageWidth) { completeCrop.width = imageWidth - completeCrop.x; completeCrop.height = completeCrop.width / crop.aspect; } return completeCrop; } function convertToPercentCrop(crop, imageWidth, imageHeight) { if (crop.unit === "%") { return crop; } return { unit: "%", aspect: crop.aspect, x: (crop.x / imageWidth) * 100, y: (crop.y / imageHeight) * 100, width: (crop.width / imageWidth) * 100, height: (crop.height / imageHeight) * 100 }; } function convertToPixelCrop(crop, imageWidth, imageHeight) { if (!crop.unit) { return { ...crop, unit: "px" }; } if (crop.unit === "px") { return crop; } return { unit: "px", aspect: crop.aspect, x: (crop.x * imageWidth) / 100, y: (crop.y * imageHeight) / 100, width: (crop.width * imageWidth) / 100, height: (crop.height * imageHeight) / 100 }; } function isAspectInvalid(crop, imageWidth, imageHeight) { if ((!crop.width && crop.height) || (crop.width && !crop.height)) { return true; } if (crop.y + crop.height > imageHeight || crop.x + crop.width > imageWidth) { return true; } // Allow a 1px tolerance due to %->px rounding. if ( crop.width / crop.aspect < crop.height - 1 || crop.width / crop.aspect > crop.height + 1 ) { return true; } if ( crop.height * crop.aspect < crop.width - 1 || crop.height * crop.aspect > crop.width + 1 ) { return true; } return false; } function resolveCrop(pixelCrop, imageWidth, imageHeight) { if (!pixelCrop) { return pixelCrop; } let fixedCrop = pixelCrop; const widthOverflows = pixelCrop.x + pixelCrop.width > imageWidth; const heightOverflows = pixelCrop.y + pixelCrop.height > imageHeight; if (widthOverflows && heightOverflows) { fixedCrop = { unit: "px", x: 0, y: 0, width: imageWidth > pixelCrop.width ? pixelCrop.width : imageWidth, height: imageHeight > pixelCrop.height ? pixelCrop.height : imageHeight }; } else if (widthOverflows) { fixedCrop = { ...pixelCrop, x: 0, width: imageWidth > pixelCrop.width ? pixelCrop.width : imageWidth }; } else if (heightOverflows) { fixedCrop = { ...pixelCrop, y: 0, height: imageHeight > pixelCrop.height ? pixelCrop.height : imageHeight }; } if (fixedCrop.aspect && isAspectInvalid(fixedCrop, imageWidth, imageHeight)) { return makeAspectCrop(fixedCrop, imageWidth, imageHeight); } return fixedCrop; } function containCrop(prevCrop, crop, imageWidth, imageHeight) { const pixelCrop = convertToPixelCrop(crop, imageWidth, imageHeight); const prevPixelCrop = convertToPixelCrop(prevCrop, imageWidth, imageHeight); const contained = { ...pixelCrop }; // Non-aspects are simple if (!pixelCrop.aspect) { if (pixelCrop.x < 0) { contained.x = 0; contained.width += pixelCrop.x; } else if (pixelCrop.x + pixelCrop.width > imageWidth) { contained.width = imageWidth - pixelCrop.x; } if (pixelCrop.y + pixelCrop.height > imageHeight) { contained.height = imageHeight - pixelCrop.y; } return contained; } let adjustedForX = false; if (pixelCrop.x < 0) { contained.x = 0; contained.width += pixelCrop.x; contained.height = contained.width / pixelCrop.aspect; adjustedForX = true; } else if (pixelCrop.x + pixelCrop.width > imageWidth) { contained.width = imageWidth - pixelCrop.x; contained.height = contained.width / pixelCrop.aspect; adjustedForX = true; } // If sizing in up direction we need to pin Y at the point it // would be at the boundary. if (adjustedForX && prevPixelCrop.y > contained.y) { contained.y = pixelCrop.y + (pixelCrop.height - contained.height); } let adjustedForY = false; if (contained.y + contained.height > imageHeight) { contained.height = imageHeight - pixelCrop.y; contained.width = contained.height * pixelCrop.aspect; adjustedForY = true; } // If sizing in left direction we need to pin X at the point it // would be at the boundary. if (adjustedForY && prevPixelCrop.x > contained.x) { contained.x = pixelCrop.x + (pixelCrop.width - contained.width); } return contained; } class Cropper extends PureComponent { window = typeof window !== "undefined" ? window : {}; document = typeof document !== "undefined" ? document : {}; state = {}; componentDidMount() { if (this.document.addEventListener) { const options = passiveSupported ? { passive: false } : false; this.document.addEventListener( "mousemove", this.onDocMouseTouchMove, options ); this.document.addEventListener( "touchmove", this.onDocMouseTouchMove, options ); this.document.addEventListener( "mouseup", this.onDocMouseTouchEnd, options ); this.document.addEventListener( "touchend", this.onDocMouseTouchEnd, options ); this.document.addEventListener( "touchcancel", this.onDocMouseTouchEnd, options ); this.componentRef.addEventListener("medialoaded", this.onMediaLoaded); } } componentWillUnmount() { if (this.document.removeEventListener) { this.document.removeEventListener("mousemove", this.onDocMouseTouchMove); this.document.removeEventListener("touchmove", this.onDocMouseTouchMove); this.document.removeEventListener("mouseup", this.onDocMouseTouchEnd); this.document.removeEventListener("touchend", this.onDocMouseTouchEnd); this.document.removeEventListener("touchcancel", this.onDocMouseTouchEnd); this.componentRef.removeEventListener("medialoaded", this.onMediaLoaded); } } componentDidUpdate(prevProps) { if (prevProps.crop !== this.props.crop && this.imageRef) { const { width, height } = this.imageRef; const crop = this.makeNewCrop(); const resolvedCrop = resolveCrop(crop, width, height); if (crop !== resolvedCrop) { const pixelCrop = convertToPixelCrop(resolvedCrop, width, height); const percentCrop = convertToPercentCrop(resolvedCrop, width, height); this.props.onChange(pixelCrop, percentCrop); this.props.onComplete(pixelCrop, percentCrop); } } } onCropMouseTouchDown = e => { const { crop, disabled } = this.props; const { width, height } = this.mediaDimensions; const pixelCrop = convertToPixelCrop(crop, width, height); if (disabled) { return; } e.preventDefault(); // Stop drag selection. const clientPos = getClientPos(e); // Focus for detecting keypress. if (this.componentRef.setActive) { this.componentRef.setActive({ preventScroll: true }); // IE/Edge #289 } else { this.componentRef.focus({ preventScroll: true }); // All other browsers } const { ord } = e.target.dataset; const xInversed = ord === "nw" || ord === "w" || ord === "sw"; const yInversed = ord === "nw" || ord === "n" || ord === "ne"; let cropOffset; if (pixelCrop.aspect) { cropOffset = this.getElementOffset(this.cropSelectRef); } this.evData = { clientStartX: clientPos.x, clientStartY: clientPos.y, cropStartWidth: pixelCrop.width, cropStartHeight: pixelCrop.height, cropStartX: xInversed ? pixelCrop.x + pixelCrop.width : pixelCrop.x, cropStartY: yInversed ? pixelCrop.y + pixelCrop.height : pixelCrop.y, xInversed, yInversed, xCrossOver: xInversed, yCrossOver: yInversed, startXCrossOver: xInversed, startYCrossOver: yInversed, isResize: e.target.dataset.ord, ord, cropOffset }; this.mouseDownOnCrop = true; this.setState({ cropIsActive: true }); }; onComponentMouseTouchDown = e => { const { crop, disabled, locked, keepSelection, onChange } = this.props; const componentEl = this.mediaWrapperRef.firstChild; if (e.target !== componentEl || !componentEl.contains(e.target)) { return; } if (disabled || locked || (keepSelection && isCropValid(crop))) { return; } e.preventDefault(); // Stop drag selection. const clientPos = getClientPos(e); // Focus for detecting keypress. if (this.componentRef.setActive) { this.componentRef.setActive({ preventScroll: true }); // IE/Edge #289 } else { this.componentRef.focus({ preventScroll: true }); // All other browsers } const mediaOffset = this.getElementOffset(this.mediaWrapperRef); const x = clientPos.x - mediaOffset.left; const y = clientPos.y - mediaOffset.top; const nextCrop = { unit: "px", aspect: crop ? crop.aspect : undefined, x, y, width: 0, height: 0 }; this.evData = { clientStartX: clientPos.x, clientStartY: clientPos.y, cropStartWidth: nextCrop.width, cropStartHeight: nextCrop.height, cropStartX: nextCrop.x, cropStartY: nextCrop.y, xInversed: false, yInversed: false, xCrossOver: false, yCrossOver: false, startXCrossOver: false, startYCrossOver: false, isResize: true, ord: "nw" }; this.mouseDownOnCrop = true; const { width, height } = this.mediaDimensions; onChange( convertToPixelCrop(nextCrop, width, height), convertToPercentCrop(nextCrop, width, height) ); this.setState({ cropIsActive: true, newCropIsBeingDrawn: true }); }; onDocMouseTouchMove = e => { const { crop, disabled, onChange, onDragStart } = this.props; if (disabled) { return; } if (!this.mouseDownOnCrop) { return; } e.preventDefault(); // Stop drag selection. if (!this.dragStarted) { this.dragStarted = true; onDragStart(e); } const { evData } = this; const clientPos = getClientPos(e); if (evData.isResize && crop.aspect && evData.cropOffset) { clientPos.y = this.straightenYPath(clientPos.x); } evData.xDiff = clientPos.x - evData.clientStartX; evData.yDiff = clientPos.y - evData.clientStartY; let nextCrop; if (evData.isResize) { nextCrop = this.resizeCrop(); } else { nextCrop = this.dragCrop(); } if (nextCrop !== crop) { const { width, height } = this.mediaDimensions; onChange( convertToPixelCrop(nextCrop, width, height), convertToPercentCrop(nextCrop, width, height) ); } }; onComponentKeyDown = e => { const { crop, disabled, onChange, onComplete } = this.props; if (disabled) { return; } const keyCode = e.key; let nudged = false; if (!isCropValid(crop)) { return; } const nextCrop = this.makeNewCrop(); const nudgeStep = e.shiftKey ? Cropper.nudgeStepLarge : Cropper.nudgeStep; if (keyCode === "ArrowLeft") { nextCrop.x -= nudgeStep; nudged = true; } else if (keyCode === "ArrowRight") { nextCrop.x += nudgeStep; nudged = true; } else if (keyCode === "ArrowUp") { nextCrop.y -= nudgeStep; nudged = true; } else if (keyCode === "ArrowDown") { nextCrop.y += nudgeStep; nudged = true; } if (nudged) { e.preventDefault(); // Stop drag selection. const { width, height } = this.mediaDimensions; nextCrop.x = clamp(nextCrop.x, 0, width - nextCrop.width); nextCrop.y = clamp(nextCrop.y, 0, height - nextCrop.height); const pixelCrop = convertToPixelCrop(nextCrop, width, height); const percentCrop = convertToPercentCrop(nextCrop, width, height); onChange(pixelCrop, percentCrop); onComplete(pixelCrop, percentCrop); } }; onDocMouseTouchEnd = e => { const { crop, disabled, onComplete, onDragEnd } = this.props; if (disabled) { return; } if (this.mouseDownOnCrop) { this.mouseDownOnCrop = false; this.dragStarted = false; const { width, height } = this.mediaDimensions; onDragEnd(e); onComplete( convertToPixelCrop(crop, width, height), convertToPercentCrop(crop, width, height) ); this.setState({ cropIsActive: false, newCropIsBeingDrawn: false }); } }; // When the image is loaded or when a custom component via `renderComponent` prop fires // a custom "medialoaded" event. createNewCrop() { const { width, height } = this.mediaDimensions; const crop = this.makeNewCrop(); const resolvedCrop = resolveCrop(crop, width, height); const pixelCrop = convertToPixelCrop(resolvedCrop, width, height); const percentCrop = convertToPercentCrop(resolvedCrop, width, height); return { pixelCrop, percentCrop }; } // Custom components (using `renderComponent`) should fire a custom event // called "medialoaded" when they are loaded. onMediaLoaded = () => { const { onComplete, onChange } = this.props; const { pixelCrop, percentCrop } = this.createNewCrop(); onChange(pixelCrop, percentCrop); onComplete(pixelCrop, percentCrop); }; onImageLoad(image) { const { onComplete, onChange, onImageLoaded } = this.props; // Return false from onImageLoaded if you set the crop with setState in there as otherwise // the subsequent onChange + onComplete will not have your updated crop. const res = onImageLoaded(image); if (res !== false) { const { pixelCrop, percentCrop } = this.createNewCrop(); onChange(pixelCrop, percentCrop); onComplete(pixelCrop, percentCrop); } } get mediaDimensions() { const { clientWidth, clientHeight } = this.mediaWrapperRef; return { width: clientWidth, height: clientHeight }; } getDocumentOffset() { const { clientTop = 0, clientLeft = 0 } = this.document.documentElement || {}; return { clientTop, clientLeft }; } getWindowOffset() { const { pageYOffset = 0, pageXOffset = 0 } = this.window; return { pageYOffset, pageXOffset }; } getElementOffset(el) { const rect = el.getBoundingClientRect(); const doc = this.getDocumentOffset(); const win = this.getWindowOffset(); const top = rect.top + win.pageYOffset - doc.clientTop; const left = rect.left + win.pageXOffset - doc.clientLeft; return { top, left }; } getCropStyle() { const crop = this.makeNewCrop( this.props.crop ? this.props.crop.unit : "px" ); return { top: `${crop.y}${crop.unit}`, left: `${crop.x}${crop.unit}`, width: `${crop.width}${crop.unit}`, height: `${crop.height}${crop.unit}` }; } getNewSize() { const { crop, minWidth, maxWidth, minHeight, maxHeight } = this.props; const { evData } = this; const { width, height } = this.mediaDimensions; // New width. let newWidth = evData.cropStartWidth + evData.xDiff; if (evData.xCrossOver) { newWidth = Math.abs(newWidth); } newWidth = clamp(newWidth, minWidth, maxWidth || width); // New height. let newHeight; if (crop.aspect) { newHeight = newWidth / crop.aspect; } else { newHeight = evData.cropStartHeight + evData.yDiff; } if (evData.yCrossOver) { // Cap if polarity is inversed and the height fills the y space. newHeight = Math.min(Math.abs(newHeight), evData.cropStartY); } newHeight = clamp(newHeight, minHeight, maxHeight || height); if (crop.aspect) { newWidth = clamp(newHeight * crop.aspect, 0, width); } return { width: newWidth, height: newHeight }; } dragCrop() { const nextCrop = this.makeNewCrop(); const { evData } = this; const { width, height } = this.mediaDimensions; nextCrop.x = clamp( evData.cropStartX + evData.xDiff, 0, width - nextCrop.width ); nextCrop.y = clamp( evData.cropStartY + evData.yDiff, 0, height - nextCrop.height ); return nextCrop; } resizeCrop() { const { evData } = this; const nextCrop = this.makeNewCrop(); const { ord } = evData; // On the inverse change the diff so it's the same and // the same algo applies. if (evData.xInversed) { evData.xDiff -= evData.cropStartWidth * 2; evData.xDiffPc -= evData.cropStartWidth * 2; } if (evData.yInversed) { evData.yDiff -= evData.cropStartHeight * 2; evData.yDiffPc -= evData.cropStartHeight * 2; } // New size. const newSize = this.getNewSize(); // Adjust x/y to give illusion of 'staticness' as width/height is increased // when polarity is inversed. let newX = evData.cropStartX; let newY = evData.cropStartY; if (evData.xCrossOver) { newX = nextCrop.x + (nextCrop.width - newSize.width); } if (evData.yCrossOver) { // This not only removes the little "shake" when inverting at a diagonal, but for some // reason y was way off at fast speeds moving sw->ne with fixed aspect only, I couldn't // figure out why. if (evData.lastYCrossover === false) { newY = nextCrop.y - newSize.height; } else { newY = nextCrop.y + (nextCrop.height - newSize.height); } } const { width, height } = this.mediaDimensions; const containedCrop = containCrop( this.props.crop, { unit: nextCrop.unit, x: newX, y: newY, width: newSize.width, height: newSize.height, aspect: nextCrop.aspect }, width, height ); // Apply x/y/width/height changes depending on ordinate (fixed aspect always applies both). if (nextCrop.aspect || Cropper.xyOrds.indexOf(ord) > -1) { nextCrop.x = containedCrop.x; nextCrop.y = containedCrop.y; nextCrop.width = containedCrop.width; nextCrop.height = containedCrop.height; } else if (Cropper.xOrds.indexOf(ord) > -1) { nextCrop.x = containedCrop.x; nextCrop.width = containedCrop.width; } else if (Cropper.yOrds.indexOf(ord) > -1) { nextCrop.y = containedCrop.y; nextCrop.height = containedCrop.height; } evData.lastYCrossover = evData.yCrossOver; this.crossOverCheck(); return nextCrop; } straightenYPath(clientX) { const { evData } = this; const { ord } = evData; const { cropOffset, cropStartWidth, cropStartHeight } = evData; let k; let d; if (ord === "nw" || ord === "se") { k = cropStartHeight / cropStartWidth; d = cropOffset.top - cropOffset.left * k; } else { k = -cropStartHeight / cropStartWidth; d = cropOffset.top + (cropStartHeight - cropOffset.left * k); } return k * clientX + d; } createCropSelection() { const { disabled, locked, renderSelectionAddon, myRules } = this.props; const style = this.getCropStyle(); return ( <div ref={r => (this.cropSelectRef = r)} style={style} className="Cropper__crop-selection" onMouseDown={this.onCropMouseTouchDown} onTouchStart={this.onCropMouseTouchDown} tabIndex="0" > {!disabled && !locked && ( <div className="Cropper__drag-elements"> <div className="Cropper__drag-bar ord-n" data-ord="n" /> <div className="Cropper__drag-bar ord-e" data-ord="e" /> <div className="Cropper__drag-bar ord-s" data-ord="s" /> <div className="Cropper__drag-bar ord-w" data-ord="w" /> <div className="Cropper__drag-handle ord-nw" data-ord="nw" /> <div className="Cropper__drag-handle ord-n" data-ord="n" /> <div className="Cropper__drag-handle ord-ne" data-ord="ne" /> <div className="Cropper__drag-handle ord-e" data-ord="e" /> <div className="Cropper__drag-handle ord-se" data-ord="se" /> <div className="Cropper__drag-handle ord-s" data-ord="s" /> <div className="Cropper__drag-handle ord-sw" data-ord="sw" /> <div className="Cropper__drag-handle ord-w" data-ord="w" /> </div> )} {renderSelectionAddon && ( <div className="Cropper__selection-addon" onMouseDown={e => e.stopPropagation()} > {renderSelectionAddon(this.state)} </div> )} {myRules && ( <div> <div className="Cropper__rule-of-thirds-hz" /> <div className="Cropper__rule-of-thirds-vt" /> </div> )} </div> ); } makeNewCrop(unit = "px") { const crop = { ...Cropper.defaultCrop, ...this.props.crop }; const { width, height } = this.mediaDimensions; return unit === "px" ? convertToPixelCrop(crop, width, height) : convertToPercentCrop(crop, width, height); } crossOverCheck() { const { evData } = this; const { minWidth, minHeight } = this.props; if ( !minWidth && ((!evData.xCrossOver && -Math.abs(evData.cropStartWidth) - evData.xDiff >= 0) || (evData.xCrossOver && -Math.abs(evData.cropStartWidth) - evData.xDiff <= 0)) ) { evData.xCrossOver = !evData.xCrossOver; } if ( !minHeight && ((!evData.yCrossOver && -Math.abs(evData.cropStartHeight) - evData.yDiff >= 0) || (evData.yCrossOver && -Math.abs(evData.cropStartHeight) - evData.yDiff <= 0)) ) { evData.yCrossOver = !evData.yCrossOver; } const swapXOrd = evData.xCrossOver !== evData.startXCrossOver; const swapYOrd = evData.yCrossOver !== evData.startYCrossOver; evData.inversedXOrd = swapXOrd ? inverseOrd(evData.ord) : false; evData.inversedYOrd = swapYOrd ? inverseOrd(evData.ord) : false; } render() { const { children, circularCrop, className, crossorigin, crop, disabled, locked, imageAlt, onImageError, renderComponent, src, style, imageStyle, myRules } = this.props; const { cropIsActive, newCropIsBeingDrawn } = this.state; const cropSelection = isCropValid(crop) && this.componentRef ? this.createCropSelection() : null; const componentClasses = clsx("Cropper", className, { "Cropper--active": cropIsActive, "Cropper--disabled": disabled, "Cropper--locked": locked, "Cropper--new-crop": newCropIsBeingDrawn, "Cropper--fixed-aspect": crop && crop.aspect, // In this case we have to shadow the image, since the box-shadow on the crop won't work. "Cropper--crop-invisible": crop && cropIsActive && (!crop.width || !crop.height), "Cropper--circular-crop": crop && circularCrop, "Cropper--rule-of-thirds": crop && myRules }); return ( <div ref={n => { this.componentRef = n; }} className={componentClasses} style={style} onTouchStart={this.onComponentMouseTouchDown} onMouseDown={this.onComponentMouseTouchDown} tabIndex="0" onKeyDown={this.onComponentKeyDown} > <div ref={n => { this.mediaWrapperRef = n; }} > {renderComponent || ( <img ref={r => (this.imageRef = r)} crossOrigin={crossorigin} className="Cropper__image" style={imageStyle} src={src} onLoad={e => this.onImageLoad(e.target)} onError={onImageError} alt={imageAlt} /> )} </div> {children} {cropSelection} </div> ); } } Cropper.xOrds = ["e", "w"]; Cropper.yOrds = ["n", "s"]; Cropper.xyOrds = ["nw", "ne", "se", "sw"]; Cropper.nudgeStep = 0.2; Cropper.nudgeStepLarge = 2; Cropper.defaultCrop = { x: 0, y: 0, width: 0, height: 0, unit: "px" }; Cropper.propTypes = { className: PropTypes.string, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node ]), circularCrop: PropTypes.bool, crop: PropTypes.shape({ aspect: PropTypes.number, x: PropTypes.number, y: PropTypes.number, width: PropTypes.number, height: PropTypes.number, unit: PropTypes.oneOf(["px", "%"]) }), crossorigin: PropTypes.string, disabled: PropTypes.bool, locked: PropTypes.bool, imageAlt: PropTypes.string, imageStyle: PropTypes.shape({}), keepSelection: PropTypes.bool, minWidth: PropTypes.number, minHeight: PropTypes.number, maxWidth: PropTypes.number, maxHeight: PropTypes.number, onChange: PropTypes.func.isRequired, onImageError: PropTypes.func, onComplete: PropTypes.func, onImageLoaded: PropTypes.func, onDragStart: PropTypes.func, onDragEnd: PropTypes.func, src: PropTypes.string.isRequired, style: PropTypes.shape({}), renderComponent: PropTypes.node, renderSelectionAddon: PropTypes.func, myRules: PropTypes.bool }; Cropper.defaultProps = { circularCrop: false, className: undefined, crop: undefined, crossorigin: undefined, disabled: false, locked: false, imageAlt: "", maxWidth: undefined, maxHeight: undefined, minWidth: 0, minHeight: 0, keepSelection: false, onComplete: () => {}, onImageError: () => {}, onImageLoaded: () => {}, onDragStart: () => {}, onDragEnd: () => {}, children: undefined, style: undefined, renderComponent: undefined, imageStyle: undefined, renderSelectionAddon: undefined, myRules: false }; export { Cropper as default, Cropper as Component, makeAspectCrop, containCrop };