Files
klz-cables.com/.pnpm-store/v10/files/7d/5339487c81d8b2ab3a52686ebc0816412cb1fc89243371205093daa485021e96babad64e4b3f5c8b4522a605a43ecbe4e3fa969ddbf15fce783e9e6fa81afe
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

821 lines
24 KiB
Plaintext

import React, { PureComponent, createRef } from 'react'
import { Ords, XYOrds, Crop, PixelCrop, PercentCrop } from './types'
import {
defaultCrop,
clamp,
cls,
areCropsEqual,
convertToPercentCrop,
convertToPixelCrop,
containCrop,
nudgeCrop,
} from './utils'
import './ReactCrop.scss'
interface EVData {
startClientX: number
startClientY: number
startCropX: number
startCropY: number
clientX: number
clientY: number
isResize: boolean
ord?: Ords
}
interface Rectangle {
x: number
y: number
width: number
height: number
}
const DOC_MOVE_OPTS = { capture: true, passive: false }
export interface ReactCropProps {
/** An object of labels to override the built-in English ones */
ariaLabels?: {
cropArea: string
nwDragHandle: string
nDragHandle: string
neDragHandle: string
eDragHandle: string
seDragHandle: string
sDragHandle: string
swDragHandle: string
wDragHandle: string
}
/** The aspect ratio of the crop, e.g. `1` for a square or `16 / 9` for landscape. */
aspect?: number
/** Classes to pass to the `ReactCrop` element. */
className?: string
/** The elements that you want to perform a crop on. For example
* an image or video. */
children?: React.ReactNode
/** Show the crop area as a circle. If your aspect is not 1 (a square) then the circle will be warped into an oval shape. Defaults to false. */
circularCrop?: boolean
/** Since v10 all crop params are required except for aspect. Omit the entire crop object if you don't want a crop. See README on how to create an aspect crop with a % crop. */
crop?: Crop
/** If true then the user cannot resize or draw a new crop. A class of `ReactCrop--disabled` is also added to the container for user styling. */
disabled?: boolean
/** If true then the user cannot create or resize a crop, but can still drag the existing crop around. A class of `ReactCrop--locked` is also added to the container for user styling. */
locked?: boolean
/** If true is passed then selection can't be disabled if the user clicks outside the selection area. */
keepSelection?: boolean
/** A minimum crop width, in pixels. */
minWidth?: number
/** A minimum crop height, in pixels. */
minHeight?: number
/** A maximum crop width, in pixels. */
maxWidth?: number
/** A maximum crop height, in pixels. */
maxHeight?: number
/** A callback which happens for every change of the crop. You should set the crop to state and pass it back into the library via the `crop` prop. */
onChange: (crop: PixelCrop, percentageCrop: PercentCrop) => void
/** A callback which happens after a resize, drag, or nudge. Passes the current crop state object in pixels and percent. */
onComplete?: (crop: PixelCrop, percentageCrop: PercentCrop) => void
/** A callback which happens when a user starts dragging or resizing. It is convenient to manipulate elements outside this component. */
onDragStart?: (e: PointerEvent) => void
/** A callback which happens when a user releases the cursor or touch after dragging or resizing. */
onDragEnd?: (e: PointerEvent) => void
/** Render a custom element in crop selection. */
renderSelectionAddon?: (state: ReactCropState) => React.ReactNode
/** Show rule of thirds lines in the cropped area. Defaults to false. */
ruleOfThirds?: boolean
/** Inline styles object to be passed to the `ReactCrop` element. */
style?: React.CSSProperties
}
export interface ReactCropState {
cropIsActive: boolean
newCropIsBeingDrawn: boolean
}
export class ReactCrop extends PureComponent<ReactCropProps, ReactCropState> {
static xOrds = ['e', 'w']
static yOrds = ['n', 's']
static xyOrds = ['nw', 'ne', 'se', 'sw']
static nudgeStep = 1
static nudgeStepMedium = 10
static nudgeStepLarge = 100
static 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',
},
}
get document() {
return document
}
docMoveBound = false
mouseDownOnCrop = false
dragStarted = false
evData: EVData = {
startClientX: 0,
startClientY: 0,
startCropX: 0,
startCropY: 0,
clientX: 0,
clientY: 0,
isResize: true,
}
componentRef = createRef<HTMLDivElement>()
mediaRef = createRef<HTMLDivElement>()
resizeObserver?: ResizeObserver
initChangeCalled = false
state: ReactCropState = {
cropIsActive: false,
newCropIsBeingDrawn: false,
}
// We unfortunately get the bounding box every time as x+y changes
// due to scrolling.
getBox(): Rectangle {
const el = this.mediaRef.current
if (!el) {
return { x: 0, y: 0, width: 0, height: 0 }
}
const { x, y, width, height } = el.getBoundingClientRect()
return { x, y, width, height }
}
componentDidUpdate(prevProps: ReactCropProps) {
const { crop, onComplete } = this.props
// Useful for when programatically setting a new
// crop and wanting to show a preview.
if (onComplete && !prevProps.crop && crop) {
const { width, height } = this.getBox()
if (width && height) {
onComplete(convertToPixelCrop(crop, width, height), convertToPercentCrop(crop, width, height))
}
}
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect()
}
}
bindDocMove() {
if (this.docMoveBound) {
return
}
this.document.addEventListener('pointermove', this.onDocPointerMove, DOC_MOVE_OPTS)
this.document.addEventListener('pointerup', this.onDocPointerDone, DOC_MOVE_OPTS)
this.document.addEventListener('pointercancel', this.onDocPointerDone, DOC_MOVE_OPTS)
this.docMoveBound = true
}
unbindDocMove() {
if (!this.docMoveBound) {
return
}
this.document.removeEventListener('pointermove', this.onDocPointerMove, DOC_MOVE_OPTS)
this.document.removeEventListener('pointerup', this.onDocPointerDone, DOC_MOVE_OPTS)
this.document.removeEventListener('pointercancel', this.onDocPointerDone, DOC_MOVE_OPTS)
this.docMoveBound = false
}
onCropPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
const { crop, disabled } = this.props
const box = this.getBox()
if (!crop) {
return
}
const pixelCrop = convertToPixelCrop(crop, box.width, box.height)
if (disabled) {
return
}
if (e.cancelable) e.preventDefault() // Stop drag selection.
// Bind to doc to follow movements outside of element.
this.bindDocMove()
// Focus for detecting keypress.
;(this.componentRef.current as HTMLDivElement).focus({ preventScroll: true })
const ord = (e.target as HTMLElement).dataset.ord as Ords
const isResize = Boolean(ord)
let startClientX = e.clientX
let startClientY = e.clientY
let startCropX = pixelCrop.x
let startCropY = pixelCrop.y
// Set the starting coords to the opposite corner.
if (ord) {
const relativeX = e.clientX - box.x
const relativeY = e.clientY - box.y
let fromCornerX = 0
let fromCornerY = 0
if (ord === 'ne' || ord == 'e') {
fromCornerX = relativeX - (pixelCrop.x + pixelCrop.width)
fromCornerY = relativeY - pixelCrop.y
startCropX = pixelCrop.x
startCropY = pixelCrop.y + pixelCrop.height
} else if (ord === 'se' || ord === 's') {
fromCornerX = relativeX - (pixelCrop.x + pixelCrop.width)
fromCornerY = relativeY - (pixelCrop.y + pixelCrop.height)
startCropX = pixelCrop.x
startCropY = pixelCrop.y
} else if (ord === 'sw' || ord == 'w') {
fromCornerX = relativeX - pixelCrop.x
fromCornerY = relativeY - (pixelCrop.y + pixelCrop.height)
startCropX = pixelCrop.x + pixelCrop.width
startCropY = pixelCrop.y
} else if (ord === 'nw' || ord == 'n') {
fromCornerX = relativeX - pixelCrop.x
fromCornerY = relativeY - pixelCrop.y
startCropX = pixelCrop.x + pixelCrop.width
startCropY = pixelCrop.y + pixelCrop.height
}
startClientX = startCropX + box.x + fromCornerX
startClientY = startCropY + box.y + fromCornerY
}
this.evData = {
startClientX,
startClientY,
startCropX,
startCropY,
clientX: e.clientX,
clientY: e.clientY,
isResize,
ord,
}
this.mouseDownOnCrop = true
this.setState({ cropIsActive: true })
}
onComponentPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
const { crop, disabled, locked, keepSelection, onChange } = this.props
const box = this.getBox()
if (disabled || locked || (keepSelection && crop)) {
return
}
if (e.cancelable) e.preventDefault() // Stop drag selection.
// Bind to doc to follow movements outside of element.
this.bindDocMove()
// Focus for detecting keypress.
;(this.componentRef.current as HTMLDivElement).focus({ preventScroll: true })
const cropX = e.clientX - box.x
const cropY = e.clientY - box.y
const nextCrop: PixelCrop = {
unit: 'px',
x: cropX,
y: cropY,
width: 0,
height: 0,
}
this.evData = {
startClientX: e.clientX,
startClientY: e.clientY,
startCropX: cropX,
startCropY: cropY,
clientX: e.clientX,
clientY: e.clientY,
isResize: true,
}
this.mouseDownOnCrop = true
onChange(convertToPixelCrop(nextCrop, box.width, box.height), convertToPercentCrop(nextCrop, box.width, box.height))
this.setState({ cropIsActive: true, newCropIsBeingDrawn: true })
}
onDocPointerMove = (e: PointerEvent) => {
const { crop, disabled, onChange, onDragStart } = this.props
const box = this.getBox()
if (disabled || !crop || !this.mouseDownOnCrop) {
return
}
// Stop drag selection.
if (e.cancelable) e.preventDefault()
if (!this.dragStarted) {
this.dragStarted = true
if (onDragStart) {
onDragStart(e)
}
}
// Update pointer position.
const { evData } = this
evData.clientX = e.clientX
evData.clientY = e.clientY
let nextCrop
if (evData.isResize) {
nextCrop = this.resizeCrop()
} else {
nextCrop = this.dragCrop()
}
if (!areCropsEqual(crop, nextCrop)) {
onChange(
convertToPixelCrop(nextCrop, box.width, box.height),
convertToPercentCrop(nextCrop, box.width, box.height)
)
}
}
onComponentKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const { crop, disabled, onChange, onComplete } = this.props
if (disabled) {
return
}
const keyCode = e.key
let nudged = false
if (!crop) {
return
}
const box = this.getBox()
const nextCrop = this.makePixelCrop(box)
const ctrlCmdPressed = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
const nudgeStep = ctrlCmdPressed
? ReactCrop.nudgeStepLarge
: e.shiftKey
? ReactCrop.nudgeStepMedium
: ReactCrop.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) {
if (e.cancelable) e.preventDefault() // Stop drag selection.
nextCrop.x = clamp(nextCrop.x, 0, box.width - nextCrop.width)
nextCrop.y = clamp(nextCrop.y, 0, box.height - nextCrop.height)
const pixelCrop = convertToPixelCrop(nextCrop, box.width, box.height)
const percentCrop = convertToPercentCrop(nextCrop, box.width, box.height)
onChange(pixelCrop, percentCrop)
if (onComplete) {
onComplete(pixelCrop, percentCrop)
}
}
}
onHandlerKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, ord: Ords) => {
const {
aspect = 0,
crop,
disabled,
minWidth = 0,
minHeight = 0,
maxWidth,
maxHeight,
onChange,
onComplete,
} = this.props
const box = this.getBox()
if (disabled || !crop) {
return
}
// Keep the event from bubbling up to the container
if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.stopPropagation()
e.preventDefault()
} else {
return
}
const ctrlCmdPressed = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
const offset = ctrlCmdPressed
? ReactCrop.nudgeStepLarge
: e.shiftKey
? ReactCrop.nudgeStepMedium
: ReactCrop.nudgeStep
const pixelCrop = convertToPixelCrop(crop, box.width, box.height)
const nudgedCrop = nudgeCrop(pixelCrop, e.key, offset, ord)
const containedCrop = containCrop(
nudgedCrop,
aspect,
ord,
box.width,
box.height,
minWidth,
minHeight,
maxWidth,
maxHeight
)
if (!areCropsEqual(crop, containedCrop)) {
const percentCrop = convertToPercentCrop(containedCrop, box.width, box.height)
onChange(containedCrop, percentCrop)
if (onComplete) {
onComplete(containedCrop, percentCrop)
}
}
}
onDocPointerDone = (e: PointerEvent) => {
const { crop, disabled, onComplete, onDragEnd } = this.props
const box = this.getBox()
this.unbindDocMove()
if (disabled || !crop) {
return
}
if (this.mouseDownOnCrop) {
this.mouseDownOnCrop = false
this.dragStarted = false
onDragEnd && onDragEnd(e)
onComplete &&
onComplete(convertToPixelCrop(crop, box.width, box.height), convertToPercentCrop(crop, box.width, box.height))
this.setState({ cropIsActive: false, newCropIsBeingDrawn: false })
}
}
onDragFocus = (e: React.FocusEvent<HTMLDivElement, Element>) => {
// Fixes #491
this.componentRef.current?.scrollTo(0, 0)
}
getCropStyle() {
const { crop } = this.props
if (!crop) {
return undefined
}
return {
top: `${crop.y}${crop.unit}`,
left: `${crop.x}${crop.unit}`,
width: `${crop.width}${crop.unit}`,
height: `${crop.height}${crop.unit}`,
}
}
dragCrop() {
const { evData } = this
const box = this.getBox()
const nextCrop = this.makePixelCrop(box)
const xDiff = evData.clientX - evData.startClientX
const yDiff = evData.clientY - evData.startClientY
nextCrop.x = clamp(evData.startCropX + xDiff, 0, box.width - nextCrop.width)
nextCrop.y = clamp(evData.startCropY + yDiff, 0, box.height - nextCrop.height)
return nextCrop
}
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
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'
} else {
return topHalf ? 'ne' : 'se'
}
}
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]
}
if (aspect > 1) {
return mw ? [mw, mw / aspect] : [mh * aspect, mh]
} else {
return mh ? [mh * aspect, mh] : [mw, mw / aspect]
}
}
resizeCrop() {
const { evData } = this
const { aspect = 0, maxWidth, maxHeight } = this.props
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, 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 dragged when going
// beyond the other side #554
if ((minWidth && ord === 'nw') || ord === 'w' || ord === 'sw') {
xDiff = Math.min(xDiff, -minWidth)
}
if ((minHeight && ord === 'nw') || ord === 'n' || ord === 'ne') {
yDiff = Math.min(yDiff, -minHeight)
}
const tmpCrop: PixelCrop = {
unit: 'px',
x: 0,
y: 0,
width: 0,
height: 0,
}
if (area === 'ne') {
tmpCrop.x = evData.startCropX
tmpCrop.width = xDiff
if (aspect) {
tmpCrop.height = tmpCrop.width / aspect
tmpCrop.y = evData.startCropY - tmpCrop.height
} else {
tmpCrop.height = Math.abs(yDiff)
tmpCrop.y = evData.startCropY - tmpCrop.height
}
} else if (area === 'se') {
tmpCrop.x = evData.startCropX
tmpCrop.y = evData.startCropY
tmpCrop.width = xDiff
if (aspect) {
tmpCrop.height = tmpCrop.width / aspect
} else {
tmpCrop.height = yDiff
}
} else if (area === 'sw') {
tmpCrop.x = evData.startCropX + xDiff
tmpCrop.y = evData.startCropY
tmpCrop.width = Math.abs(xDiff)
if (aspect) {
tmpCrop.height = tmpCrop.width / aspect
} else {
tmpCrop.height = yDiff
}
} else if (area === 'nw') {
tmpCrop.x = evData.startCropX + xDiff
tmpCrop.width = Math.abs(xDiff)
if (aspect) {
tmpCrop.height = tmpCrop.width / aspect
tmpCrop.y = evData.startCropY - tmpCrop.height
} else {
tmpCrop.height = Math.abs(yDiff)
tmpCrop.y = evData.startCropY + yDiff
}
}
const containedCrop = containCrop(
tmpCrop,
aspect,
area,
box.width,
box.height,
minWidth,
minHeight,
maxWidth,
maxHeight
)
// Apply x/y/width/height changes depending on ordinate
// (fixed aspect always applies both).
if (aspect || ReactCrop.xyOrds.indexOf(ord) > -1) {
nextCrop = containedCrop
} else if (ReactCrop.xOrds.indexOf(ord) > -1) {
nextCrop.x = containedCrop.x
nextCrop.width = containedCrop.width
} else if (ReactCrop.yOrds.indexOf(ord) > -1) {
nextCrop.y = containedCrop.y
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
}
createCropSelection() {
const {
ariaLabels = ReactCrop.defaultProps.ariaLabels,
disabled,
locked,
renderSelectionAddon,
ruleOfThirds,
crop,
} = this.props
const style = this.getCropStyle()
if (!crop) {
return undefined
}
return (
<div
style={style}
className="ReactCrop__crop-selection"
onPointerDown={this.onCropPointerDown}
aria-label={ariaLabels.cropArea}
tabIndex={0}
onKeyDown={this.onComponentKeyDown}
role="group"
>
{!disabled && !locked && (
<div className="ReactCrop__drag-elements" onFocus={this.onDragFocus}>
<div className="ReactCrop__drag-bar ord-n" data-ord="n" />
<div className="ReactCrop__drag-bar ord-e" data-ord="e" />
<div className="ReactCrop__drag-bar ord-s" data-ord="s" />
<div className="ReactCrop__drag-bar ord-w" data-ord="w" />
<div
className="ReactCrop__drag-handle ord-nw"
data-ord="nw"
tabIndex={0}
aria-label={ariaLabels.nwDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'nw')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-n"
data-ord="n"
tabIndex={0}
aria-label={ariaLabels.nDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'n')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-ne"
data-ord="ne"
tabIndex={0}
aria-label={ariaLabels.neDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'ne')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-e"
data-ord="e"
tabIndex={0}
aria-label={ariaLabels.eDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'e')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-se"
data-ord="se"
tabIndex={0}
aria-label={ariaLabels.seDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'se')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-s"
data-ord="s"
tabIndex={0}
aria-label={ariaLabels.sDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 's')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-sw"
data-ord="sw"
tabIndex={0}
aria-label={ariaLabels.swDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'sw')}
role="button"
/>
<div
className="ReactCrop__drag-handle ord-w"
data-ord="w"
tabIndex={0}
aria-label={ariaLabels.wDragHandle}
onKeyDown={e => this.onHandlerKeyDown(e, 'w')}
role="button"
/>
</div>
)}
{renderSelectionAddon && (
<div className="ReactCrop__selection-addon" onMouseDown={e => e.stopPropagation()}>
{renderSelectionAddon(this.state)}
</div>
)}
{ruleOfThirds && (
<>
<div className="ReactCrop__rule-of-thirds-hz" />
<div className="ReactCrop__rule-of-thirds-vt" />
</>
)}
</div>
)
}
makePixelCrop(box: Rectangle) {
const crop = { ...defaultCrop, ...(this.props.crop || {}) }
return convertToPixelCrop(crop, box.width, box.height)
}
render() {
const { aspect, children, circularCrop, className, crop, disabled, locked, style, ruleOfThirds } = this.props
const { cropIsActive, newCropIsBeingDrawn } = this.state
const cropSelection = crop ? this.createCropSelection() : null
const componentClasses = cls(
'ReactCrop',
className,
cropIsActive && 'ReactCrop--active',
disabled && 'ReactCrop--disabled',
locked && 'ReactCrop--locked',
newCropIsBeingDrawn && 'ReactCrop--new-crop',
crop && aspect && 'ReactCrop--fixed-aspect',
crop && circularCrop && 'ReactCrop--circular-crop',
crop && ruleOfThirds && 'ReactCrop--rule-of-thirds',
!this.dragStarted && crop && !crop.width && !crop.height && 'ReactCrop--invisible-crop',
circularCrop && 'ReactCrop--no-animate'
)
return (
<div ref={this.componentRef} className={componentClasses} style={style}>
<div ref={this.mediaRef} className="ReactCrop__child-wrapper" onPointerDown={this.onComponentPointerDown}>
{children}
</div>
{cropSelection}
</div>
)
}
}