├── .babelrc
├── .gitignore
├── README.md
├── cropper.css
├── dist
├── react-crop.js
├── react-crop.js.map
├── react-crop.min.js
└── react-crop.min.js.map
├── docs
├── bundle.js
├── index.html
└── index.js
├── draggable-resizable-box.js
├── index.js
├── lib
├── cropper.css
├── draggable-resizable-box.js
└── index.src.js
├── package.json
├── webpack.config.js
└── webpack.dist.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-1"],
3 | "plugins": ["transform-object-assign"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | npm_debug.log
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #react-crop#
2 | An accessible image cropper where the image is stationary and a resizable, draggable box represents the cropped image
3 |
4 | For example usage check out the docs folder. Demo: http://instructure-react.github.io/react-crop/
5 |
6 | ###Basic usage###
7 |
8 | ``` javascript
9 | import React, { Component } from 'react';
10 |
11 | import Cropper from 'react-crop';
12 | import 'react-crop/css';
13 |
14 | // You'll need to use async functions
15 | import "babel-core/register";
16 | import "babel-polyfill";
17 |
18 | export default class MyComponent extends Component {
19 | constructor() {
20 | super();
21 |
22 | this.state = {
23 | image: null,
24 | previewImage: null
25 | };
26 | }
27 |
28 | onChange(evt) {
29 | this.setState({
30 | image: evt.target.files[0]
31 | })
32 | }
33 |
34 | async crop() {
35 | let image = await this.refs.crop.cropImage()
36 | this.setState({
37 | previewUrl: window.URL.createObjectURL(image)
38 | })
39 | }
40 |
41 | clear() {
42 | this.refs.file.value = null
43 | this.setState({
44 | previewUrl: null,
45 | image: null
46 | })
47 | }
48 |
49 | imageLoaded(img) {
50 | if (img.naturalWidth && img.naturalWidth < 262 &&
51 | img.naturalHeight && img.naturalHeight < 147) {
52 | this.crop()
53 | }
54 | }
55 |
56 | render() {
57 | return (
58 |
59 |
60 |
61 | {
62 |
63 | this.state.image &&
64 |
65 |
66 |
73 |
74 |
75 |
76 |
77 |
78 | }
79 |
80 | {
81 | this.state.previewUrl &&
82 |
83 |

84 | }
85 |
86 |
87 | );
88 | }
89 | }
90 | ```
91 |
92 | ###Props###
93 |
94 | ####`width`####
95 | This is the desired width that you would like the image to be cropped to. The width of the cropper will be scaled to fit this size. This prop also helps determine the minimum width that the cropper can be.
96 |
97 | ####`height`####
98 | This is the desired height that you would like the image to be cropped to. The height of the cropper will be scaled to fit this size. This prop also helps determine the minimum height that the cropper can be. The width and height aspect ratio will be preserved while resizing the cropper.
99 |
100 | ####`image`####
101 | A `blob` of the original image that you wish to crop.
102 |
103 | ####`widthLabel`####
104 | The label to use next to the width input used for keyboard users. This is especially useful if you need to localize the text. The default is "Width".
105 |
106 | ####`heightLabel`####
107 | The label to use next to the height input used for keyboard users. This is especially useful if you need to localize the text. The default is "Height".
108 |
109 | ####`offsetXLabel`####
110 | The label to use next to the offset X input used for keyboard users. This is especially useful if you need to localize the text. The default is "Offset X".
111 |
112 | ####`offsetYLabel`####
113 | The label to use next to the offset Y input used for keyboard users. This is especially useful if you need to localize the text. The default is "Offset Y".
114 |
115 | ###Running the Example###
116 | - Clone the repo
117 | - `npm i`
118 | - `npm run docs`
119 | - Visit `localhost:8080`
120 |
--------------------------------------------------------------------------------
/cropper.css:
--------------------------------------------------------------------------------
1 | .Cropper {
2 | position: relative;
3 | display: inline-block;
4 | max-width:100%;
5 | max-height:100%;
6 | }
7 |
8 | .box {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | bottom: 0;
13 | right: 0;
14 | }
15 |
16 | .Cropper-box {
17 | position: absolute;
18 | top: 0;
19 | left: 0;
20 | bottom: 0;
21 | right: 0;
22 | cursor: move;
23 | border: #fff solid 1px;
24 | }
25 |
26 | .Cropper-canvas {
27 | visibility: hidden;
28 | position: absolute;
29 | }
30 |
31 | .Cropper-image {
32 | vertical-align: middle;
33 | max-width: 100%;
34 | position: relative;
35 | transform: translate(-50%, -50%);
36 | left: 50%;
37 | }
38 |
39 | .resize-handle {
40 | position: absolute;
41 | background-color: #ECEEEF;
42 | border: #8295AB solid 1px;
43 | width: 13px;
44 | height: 13px;
45 | z-index: 1;
46 | }
47 |
48 | .resize-handle-se {
49 | bottom: 0;
50 | right: 0;
51 | cursor: nwse-resize;
52 | transform: translate(50%, 50%);
53 | }
54 | .resize-handle-ne {
55 | right: 0;
56 | top: 0;
57 | cursor: nesw-resize;
58 | transform: translate(50%, -50%);
59 | }
60 | .resize-handle-sw {
61 | bottom: 0;
62 | left: 0;
63 | cursor: nesw-resize;
64 | transform: translate(-50%, 50%);
65 | }
66 | .resize-handle-nw {
67 | top: 0;
68 | bottom: 0;
69 | cursor: nwse-resize;
70 | transform: translate(-50%, -50%);
71 | }
72 |
73 | .DraggableResizable {
74 | position: relative;
75 | width: 100%;
76 | height: 100%;
77 | }
78 |
79 | .DraggableResizable-controls {
80 | border: 0;
81 | clip: rect(0 0 0 0);
82 | height: 1px;
83 | margin: -1px;
84 | overflow: hidden;
85 | padding: 0;
86 | position: absolute;
87 | width: 1px;
88 | }
89 |
90 | .DraggableResizable-top,
91 | .DraggableResizable-left,
92 | .DraggableResizable-bottom,
93 | .DraggableResizable-right {
94 | position: absolute;
95 | background-color: rgba(0,0,0,.7);
96 | }
97 |
98 | .DraggableResizable-top {
99 | top: 0;
100 | left: 0;
101 | right: 0;
102 | }
103 | .DraggableResizable-bottom {
104 | bottom: 0;
105 | left: 0;
106 | right: 0;
107 | }
108 | .DraggableResizable-left {
109 | left: 0;
110 | }
111 | .DraggableResizable-right {
112 | right: 0;
113 | }
114 |
--------------------------------------------------------------------------------
/dist/react-crop.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("react"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["react"], factory);
6 | else if(typeof exports === 'object')
7 | exports["ReactCrop"] = factory(require("react"));
8 | else
9 | root["ReactCrop"] = factory(root["react"]);
10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 | /******/
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ exports: {},
25 | /******/ id: moduleId,
26 | /******/ loaded: false
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.loaded = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // __webpack_public_path__
47 | /******/ __webpack_require__.p = "/";
48 | /******/
49 | /******/ // Load entry module and return exports
50 | /******/ return __webpack_require__(0);
51 | /******/ })
52 | /************************************************************************/
53 | /******/ ([
54 | /* 0 */
55 | /***/ function(module, exports, __webpack_require__) {
56 |
57 | 'use strict';
58 |
59 | Object.defineProperty(exports, "__esModule", {
60 | value: true
61 | });
62 |
63 | var _react = __webpack_require__(1);
64 |
65 | var _react2 = _interopRequireDefault(_react);
66 |
67 | var _draggableResizableBox = __webpack_require__(2);
68 |
69 | var _draggableResizableBox2 = _interopRequireDefault(_draggableResizableBox);
70 |
71 | var _dataUriToBlob = __webpack_require__(3);
72 |
73 | var _dataUriToBlob2 = _interopRequireDefault(_dataUriToBlob);
74 |
75 | __webpack_require__(4);
76 |
77 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
78 |
79 | exports.default = _react2.default.createClass({
80 | displayName: 'Cropper',
81 |
82 | propTypes: {
83 | width: _react2.default.PropTypes.number.isRequired,
84 | height: _react2.default.PropTypes.number.isRequired,
85 | center: _react2.default.PropTypes.bool,
86 | image: _react2.default.PropTypes.any,
87 | widthLabel: _react2.default.PropTypes.string,
88 | heightLabel: _react2.default.PropTypes.string,
89 | offsetXLabel: _react2.default.PropTypes.string,
90 | offsetYLabel: _react2.default.PropTypes.string,
91 | onImageLoaded: _react2.default.PropTypes.func,
92 | minConstraints: _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.number)
93 | },
94 |
95 | getDefaultProps: function getDefaultProps() {
96 | return {
97 | center: false,
98 | width: 'Width',
99 | height: 'Height',
100 | offsetXLabel: 'Offset X',
101 | offsetYLabel: 'Offset Y'
102 | };
103 | },
104 | getInitialState: function getInitialState() {
105 | return {
106 | imageLoaded: false,
107 | width: this.props.width,
108 | height: this.props.height,
109 | url: window.URL.createObjectURL(this.props.image)
110 | };
111 | },
112 | componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
113 | if (this.props.image !== nextProps.image) {
114 | this.setState({
115 | url: window.URL.createObjectURL(nextProps.image),
116 | imageLoaded: false
117 | });
118 | }
119 | },
120 | shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
121 | var image = this.props.image;
122 |
123 | return nextProps.image.size !== image.size || nextProps.image.name !== image.name || nextProps.image.type !== image.type || nextState.imageLoaded !== this.state.imageLoaded;
124 | },
125 | onLoad: function onLoad(evt) {
126 | var _this = this;
127 |
128 | var box = this.refs.box.getBoundingClientRect();
129 | this.setState({
130 | imageLoaded: true,
131 | width: box.width,
132 | height: box.height
133 | }, function () {
134 | var img = _this.refs.image;
135 | _this.props.onImageLoaded && _this.props.onImageLoaded(img);
136 | });
137 | },
138 | cropImage: function cropImage() {
139 | var _this2 = this;
140 |
141 | return new Promise(function (resolve, reject) {
142 | var img = new Image();
143 | img.onload = function () {
144 | var canvas = _this2.refs.canvas;
145 | var img = _this2.refs.image;
146 | var ctx = canvas.getContext('2d');
147 | var xScale = img.naturalWidth / _this2.state.width,
148 | yScale = img.naturalHeight / _this2.state.height;
149 |
150 |
151 | var imageOffsetX = xScale < 1 ? 0 : _this2.state.offset.left * xScale;
152 | var imageOffsetY = yScale < 1 ? 0 : _this2.state.offset.top * yScale;
153 | var imageWidth = xScale < 1 ? img.naturalWidth : _this2.state.dimensions.width * xScale;
154 | var imageHeight = yScale < 1 ? img.naturalHeight : _this2.state.dimensions.height * yScale;
155 |
156 | var canvasOffsetX = xScale < 1 ? Math.floor((_this2.state.dimensions.width - img.naturalWidth) / 2) : 0;
157 | var canvasOffsetY = yScale < 1 ? Math.floor((_this2.state.dimensions.height - img.naturalHeight) / 2) : 0;
158 | var canvasWidth = xScale < 1 ? img.naturalWidth : _this2.props.width;
159 | var canvasHeight = yScale < 1 ? img.naturalHeight : _this2.props.height;
160 |
161 | ctx.clearRect(0, 0, _this2.props.width, _this2.props.height);
162 | ctx.drawImage(img, imageOffsetX, imageOffsetY, imageWidth, imageHeight, canvasOffsetX, canvasOffsetY, canvasWidth, canvasHeight);
163 | resolve((0, _dataUriToBlob2.default)(canvas.toDataURL()));
164 | };
165 | img.src = window.URL.createObjectURL(_this2.props.image);
166 | });
167 | },
168 | onChange: function onChange(offset, dimensions) {
169 | this.setState({ offset: offset, dimensions: dimensions });
170 | },
171 | render: function render() {
172 | return _react2.default.createElement(
173 | 'div',
174 | {
175 | ref: 'box',
176 | className: 'Cropper',
177 | style: {
178 | minWidth: this.props.width,
179 | minHeight: this.props.height
180 | } },
181 | _react2.default.createElement('canvas', {
182 | className: 'Cropper-canvas',
183 | ref: 'canvas',
184 | width: this.props.width,
185 | height: this.props.height }),
186 | _react2.default.createElement('img', {
187 | ref: 'image',
188 | src: this.state.url,
189 | className: 'Cropper-image',
190 | onLoad: this.onLoad,
191 | style: { top: this.state.height / 2 } }),
192 | this.state.imageLoaded && _react2.default.createElement(
193 | 'div',
194 | { className: 'box' },
195 | _react2.default.createElement(
196 | _draggableResizableBox2.default,
197 | {
198 | aspectRatio: this.props.width / this.props.height,
199 | width: this.state.width,
200 | height: this.state.height,
201 | minConstraints: this.props.minConstraints,
202 | onChange: this.onChange,
203 | widthLabel: this.props.widthLabel,
204 | heightLabel: this.props.heightLabel,
205 | offsetXLabel: this.props.offsetXLabel,
206 | offsetYLabel: this.props.offsetYLabel },
207 | _react2.default.createElement('div', { className: 'Cropper-box' })
208 | )
209 | )
210 | );
211 | }
212 | });
213 |
214 | /***/ },
215 | /* 1 */
216 | /***/ function(module, exports) {
217 |
218 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
219 |
220 | /***/ },
221 | /* 2 */
222 | /***/ function(module, exports, __webpack_require__) {
223 |
224 | 'use strict';
225 |
226 | Object.defineProperty(exports, "__esModule", {
227 | value: true
228 | });
229 |
230 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
231 |
232 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
233 |
234 | var _react = __webpack_require__(1);
235 |
236 | var _react2 = _interopRequireDefault(_react);
237 |
238 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
239 |
240 | exports.default = _react2.default.createClass({
241 | displayName: 'DraggableResizableBox',
242 |
243 | propTypes: {
244 | aspectRatio: _react2.default.PropTypes.number.isRequired,
245 | width: _react2.default.PropTypes.number.isRequired,
246 | height: _react2.default.PropTypes.number.isRequired,
247 | onChange: _react2.default.PropTypes.func,
248 | offset: _react2.default.PropTypes.array,
249 | minConstraints: _react2.default.PropTypes.array,
250 | children: _react2.default.PropTypes.node,
251 | widthLabel: _react2.default.PropTypes.string,
252 | heightLabel: _react2.default.PropTypes.string,
253 | offsetXLabel: _react2.default.PropTypes.string,
254 | offsetYLabel: _react2.default.PropTypes.string
255 | },
256 |
257 | getDefaultProps: function getDefaultProps() {
258 | return {
259 | widthLabel: 'Width',
260 | heightLabel: 'Height',
261 | offsetXLabel: 'Offset X',
262 | offsetYLabel: 'Offset Y'
263 | };
264 | },
265 | getInitialState: function getInitialState() {
266 | var _preserveAspectRatio = this.preserveAspectRatio(this.props.width, this.props.height),
267 | _preserveAspectRatio2 = _slicedToArray(_preserveAspectRatio, 2),
268 | width = _preserveAspectRatio2[0],
269 | height = _preserveAspectRatio2[1];
270 |
271 | var centerYOffset = (this.props.height - height) / 2;
272 | var centerXOffset = (this.props.width - width) / 2;
273 | return {
274 | top: centerYOffset,
275 | left: centerXOffset,
276 | bottom: centerYOffset,
277 | right: centerXOffset,
278 | width: width,
279 | height: height
280 | };
281 | },
282 | componentDidMount: function componentDidMount() {
283 | document.addEventListener('mousemove', this.eventMove);
284 | document.addEventListener('mouseup', this.eventEnd);
285 | document.addEventListener('touchmove', this.eventMove);
286 | document.addEventListener('touchend', this.eventEnd);
287 | document.addEventListener('keydown', this.handleKey);
288 | this.props.onChange({
289 | top: this.state.top,
290 | left: this.state.left
291 | }, {
292 | width: this.state.width,
293 | height: this.state.height
294 | });
295 | },
296 | componentWillUnmount: function componentWillUnmount() {
297 | document.removeEventListener('mousemove', this.eventMove);
298 | document.removeEventListener('mouseup', this.eventEnd);
299 | document.removeEventListener('touchmove', this.eventMove);
300 | document.removeEventListener('touchend', this.eventEnd);
301 | document.removeEventListener('keydown', this.handleKey);
302 | },
303 | calculateDimensions: function calculateDimensions(_ref) {
304 | var top = _ref.top,
305 | left = _ref.left,
306 | bottom = _ref.bottom,
307 | right = _ref.right;
308 |
309 | return { width: this.props.width - left - right, height: this.props.height - top - bottom };
310 | },
311 |
312 |
313 | // If you do this, be careful of constraints
314 | preserveAspectRatio: function preserveAspectRatio(width, height) {
315 | if (this.props.minConstraints) {
316 | width = Math.max(width, this.props.minConstraints[0]);
317 | height = Math.max(height, this.props.minConstraints[1]);
318 | }
319 | var currentAspectRatio = width / height;
320 |
321 | if (currentAspectRatio < this.props.aspectRatio) {
322 | return [width, width / this.props.aspectRatio];
323 | } else if (currentAspectRatio > this.props.aspectRatio) {
324 | return [height * this.props.aspectRatio, height];
325 | } else {
326 | return [width, height];
327 | }
328 | },
329 | constrainBoundary: function constrainBoundary(side) {
330 | return side < 0 ? 0 : side;
331 | },
332 | getClientCoordinates: function getClientCoordinates(evt) {
333 | return evt.touches ? {
334 | clientX: evt.touches[0].clientX,
335 | clientY: evt.touches[0].clientY
336 | } : {
337 | clientX: evt.clientX,
338 | clientY: evt.clientY
339 | };
340 | },
341 | eventMove: function eventMove(evt) {
342 | if (this.state.resizing) {
343 | this.onResize(evt);
344 | } else if (this.state.moving) {
345 | this.eventMoveBox(evt);
346 | }
347 | },
348 | eventEnd: function eventEnd(evt) {
349 | if (this.state.resizing) {
350 | this.stopResize(evt);
351 | } else if (this.state.moving) {
352 | this.stopMove(evt);
353 | }
354 | },
355 |
356 |
357 | // Resize methods
358 | startResize: function startResize(corner, event) {
359 | event.stopPropagation();
360 | event.preventDefault();
361 | this.setState({
362 | resizing: true,
363 | corner: corner
364 | });
365 | },
366 | stopResize: function stopResize() {
367 | this.setState({ resizing: false });
368 | },
369 |
370 |
371 | // resize strategies
372 | nw: function nw(mousePos, boxPos) {
373 | var pos = _extends({}, this.state, {
374 | top: this.constrainBoundary(mousePos.clientY - boxPos.top),
375 | left: this.constrainBoundary(mousePos.clientX - boxPos.left)
376 | });
377 | var dimensions = this.calculateDimensions(pos);
378 |
379 | var _preserveAspectRatio3 = this.preserveAspectRatio(dimensions.width, dimensions.height),
380 | _preserveAspectRatio4 = _slicedToArray(_preserveAspectRatio3, 2),
381 | width = _preserveAspectRatio4[0],
382 | height = _preserveAspectRatio4[1];
383 |
384 | pos.top = this.props.height - pos.bottom - height;
385 | pos.left = this.props.width - pos.right - width;
386 | return pos;
387 | },
388 | ne: function ne(mousePos, boxPos) {
389 | var pos = _extends({}, this.state, {
390 | top: this.constrainBoundary(mousePos.clientY - boxPos.top),
391 | right: this.constrainBoundary(boxPos.right - mousePos.clientX)
392 | });
393 | var dimensions = this.calculateDimensions(pos);
394 |
395 | var _preserveAspectRatio5 = this.preserveAspectRatio(dimensions.width, dimensions.height),
396 | _preserveAspectRatio6 = _slicedToArray(_preserveAspectRatio5, 2),
397 | width = _preserveAspectRatio6[0],
398 | height = _preserveAspectRatio6[1];
399 |
400 | pos.top = this.props.height - pos.bottom - height;
401 | pos.right = this.props.width - pos.left - width;
402 | return pos;
403 | },
404 | se: function se(mousePos, boxPos) {
405 | var pos = _extends({}, this.state, {
406 | bottom: this.constrainBoundary(boxPos.bottom - mousePos.clientY),
407 | right: this.constrainBoundary(boxPos.right - mousePos.clientX)
408 | });
409 | var dimensions = this.calculateDimensions(pos);
410 |
411 | var _preserveAspectRatio7 = this.preserveAspectRatio(dimensions.width, dimensions.height),
412 | _preserveAspectRatio8 = _slicedToArray(_preserveAspectRatio7, 2),
413 | width = _preserveAspectRatio8[0],
414 | height = _preserveAspectRatio8[1];
415 |
416 | pos.bottom = this.props.height - pos.top - height;
417 | pos.right = this.props.width - pos.left - width;
418 | return pos;
419 | },
420 | sw: function sw(mousePos, boxPos) {
421 | var pos = _extends({}, this.state, {
422 | bottom: this.constrainBoundary(boxPos.bottom - mousePos.clientY),
423 | left: this.constrainBoundary(mousePos.clientX - boxPos.left)
424 | });
425 | var dimensions = this.calculateDimensions(pos);
426 |
427 | var _preserveAspectRatio9 = this.preserveAspectRatio(dimensions.width, dimensions.height),
428 | _preserveAspectRatio10 = _slicedToArray(_preserveAspectRatio9, 2),
429 | width = _preserveAspectRatio10[0],
430 | height = _preserveAspectRatio10[1];
431 |
432 | pos.bottom = this.props.height - pos.top - height;
433 | pos.left = this.props.width - pos.right - width;
434 | return pos;
435 | },
436 | onResize: function onResize(event) {
437 | var box = this.refs.box.parentElement.parentElement.getBoundingClientRect();
438 | var coordinates = this.getClientCoordinates(event);
439 | var position = this[this.state.corner](coordinates, box);
440 | this.resize(position, coordinates);
441 | },
442 | controlsResize: function controlsResize(event) {
443 | var box = this.refs.box.parentElement.parentElement.getBoundingClientRect();
444 | var width = event.target.name === 'width' ? +event.target.value : +event.target.value * this.props.aspectRatio;
445 | var height = event.target.name === 'height' ? +event.target.value : +event.target.value / this.props.aspectRatio;
446 | var dimensions = this.preserveAspectRatio(width, height);
447 | width = dimensions[0];
448 | height = dimensions[1];
449 |
450 | if (width > box.width - this.state.left || height > box.height - this.state.top) return;
451 |
452 | var widthDifference = this.state.width - width;
453 | var heightDifference = this.state.height - height;
454 | var pos = _extends({}, this.state, {
455 | right: this.state.right + widthDifference,
456 | bottom: this.state.bottom + heightDifference
457 | });
458 | var coordinates = {
459 | clientX: box.right - pos.right,
460 | clientY: box.bottom - pos.bottom
461 | };
462 |
463 | this.resize(pos, coordinates);
464 | },
465 | resize: function resize(position, coordinates) {
466 | var _this = this;
467 |
468 | var dimensions = this.calculateDimensions(position);
469 | var widthChanged = dimensions.width !== this.state.width,
470 | heightChanged = dimensions.height !== this.state.height;
471 | if (!widthChanged && !heightChanged) return;
472 |
473 | this.setState(_extends({}, coordinates, position, dimensions), function () {
474 | _this.props.onChange({
475 | top: position.top,
476 | left: position.left
477 | }, dimensions);
478 | });
479 | },
480 |
481 |
482 | // Move methods
483 | startMove: function startMove(evt) {
484 | var _getClientCoordinates = this.getClientCoordinates(evt),
485 | clientX = _getClientCoordinates.clientX,
486 | clientY = _getClientCoordinates.clientY;
487 |
488 | this.setState({
489 | moving: true,
490 | clientX: clientX,
491 | clientY: clientY
492 | });
493 | },
494 | stopMove: function stopMove(evt) {
495 | this.setState({
496 | moving: false
497 | });
498 | },
499 | eventMoveBox: function eventMoveBox(evt) {
500 | evt.preventDefault();
501 |
502 | var _getClientCoordinates2 = this.getClientCoordinates(evt),
503 | clientX = _getClientCoordinates2.clientX,
504 | clientY = _getClientCoordinates2.clientY;
505 |
506 | var movedX = clientX - this.state.clientX;
507 | var movedY = clientY - this.state.clientY;
508 |
509 | this.moveBox(clientX, clientY, movedX, movedY);
510 | },
511 | controlsMoveBox: function controlsMoveBox(evt) {
512 | var movedX = evt.target.name === 'x' ? evt.target.value - this.state.left : 0;
513 | var movedY = evt.target.name === 'y' ? evt.target.value - this.state.top : 0;
514 | this.moveBox(0, 0, movedX, movedY);
515 | },
516 | moveBox: function moveBox(clientX, clientY, movedX, movedY) {
517 | var _this2 = this;
518 |
519 | var position = {
520 | top: this.constrainBoundary(this.state.top + movedY),
521 | left: this.constrainBoundary(this.state.left + movedX),
522 | bottom: this.constrainBoundary(this.state.bottom - movedY),
523 | right: this.constrainBoundary(this.state.right - movedX)
524 | };
525 |
526 | if (!position.top) {
527 | position.bottom = this.props.height - this.state.height;
528 | }
529 | if (!position.bottom) {
530 | position.top = this.props.height - this.state.height;
531 | }
532 | if (!position.left) {
533 | position.right = this.props.width - this.state.width;
534 | }
535 | if (!position.right) {
536 | position.left = this.props.width - this.state.width;
537 | }
538 |
539 | this.setState(_extends({}, {
540 | clientX: clientX,
541 | clientY: clientY
542 | }, position), function () {
543 | _this2.props.onChange({
544 | top: position.top,
545 | left: position.left
546 | }, _this2.calculateDimensions(position));
547 | });
548 | },
549 | keyboardResize: function keyboardResize(change) {
550 | if (this.state.right - change < 0) {
551 | return;
552 | }
553 | if (this.state.bottom - change < 0) {
554 | return;
555 | }
556 |
557 | var _preserveAspectRatio11 = this.preserveAspectRatio(this.state.width + change, this.state.height + change),
558 | _preserveAspectRatio12 = _slicedToArray(_preserveAspectRatio11, 2),
559 | width = _preserveAspectRatio12[0],
560 | height = _preserveAspectRatio12[1];
561 |
562 | var widthChange = width - this.state.width;
563 | var heightChange = height - this.state.height;
564 |
565 | this.setState({
566 | bottom: this.state.bottom - heightChange,
567 | right: this.state.right - widthChange,
568 | width: width,
569 | height: height
570 | });
571 | },
572 | handleKey: function handleKey(event) {
573 | // safari doesn't support event.key, so fall back to keyCode
574 | if (event.shiftKey) {
575 | if (event.key === 'ArrowUp' || event.keyCode === 38) {
576 | this.keyboardResize(-10);
577 | event.preventDefault();
578 | } else if (event.key === 'ArrowDown' || event.keyCode === 40) {
579 | this.keyboardResize(10);
580 | event.preventDefault();
581 | } else if (event.key === 'ArrowLeft' || event.keyCode === 37) {
582 | this.keyboardResize(-10);
583 | event.preventDefault();
584 | } else if (event.key === 'ArrowRight' || event.keyCode === 39) {
585 | this.keyboardResize(10);
586 | event.preventDefault();
587 | }
588 | } else {
589 | if (event.key === 'ArrowUp' || event.keyCode === 38) {
590 | this.moveBox(this.state.clientX, this.state.clientY, 0, -10);
591 | event.preventDefault();
592 | } else if (event.key === 'ArrowDown' || event.keyCode === 40) {
593 | this.moveBox(this.state.clientX, this.state.clientY, 0, 10);
594 | event.preventDefault();
595 | } else if (event.key === 'ArrowLeft' || event.keyCode === 37) {
596 | this.moveBox(this.state.clientX, this.state.clientY, -10, 0);
597 | event.preventDefault();
598 | } else if (event.key === 'ArrowRight' || event.keyCode === 39) {
599 | this.moveBox(this.state.clientX, this.state.clientY, 10, 0);
600 | event.preventDefault();
601 | }
602 | }
603 | },
604 | render: function render() {
605 | var style = {
606 | position: 'absolute',
607 | top: this.state.top,
608 | left: this.state.left,
609 | right: this.state.right,
610 | bottom: this.state.bottom
611 | };
612 |
613 | var _calculateDimensions = this.calculateDimensions(this.state),
614 | width = _calculateDimensions.width,
615 | height = _calculateDimensions.height;
616 |
617 | var topStyle = {
618 | height: this.state.top
619 | };
620 | var bottomStyle = {
621 | height: this.state.bottom
622 | };
623 | var leftStyle = {
624 | top: this.state.top,
625 | right: width + this.state.right,
626 | bottom: this.state.bottom
627 | };
628 | var rightStyle = {
629 | top: this.state.top,
630 | left: width + this.state.left,
631 | bottom: this.state.bottom
632 | };
633 |
634 | return _react2.default.createElement(
635 | 'div',
636 | { ref: 'box', className: 'DraggableResizable' },
637 | _react2.default.createElement(
638 | 'div',
639 | { className: 'DraggableResizable-controls' },
640 | _react2.default.createElement(
641 | 'label',
642 | null,
643 | this.props.offsetXLabel,
644 | _react2.default.createElement('input', {
645 | name: 'x',
646 | value: Math.round(this.state.left),
647 | onChange: this.controlsMoveBox,
648 | tabIndex: '-1',
649 | type: 'number' })
650 | ),
651 | _react2.default.createElement(
652 | 'label',
653 | null,
654 | this.props.offsetYLabel,
655 | _react2.default.createElement('input', {
656 | name: 'y',
657 | value: Math.round(this.state.top),
658 | onChange: this.controlsMoveBox,
659 | tabIndex: '-1',
660 | type: 'number' })
661 | ),
662 | _react2.default.createElement(
663 | 'label',
664 | null,
665 | this.props.widthLabel,
666 | _react2.default.createElement('input', {
667 | name: 'width',
668 | value: Math.round(width),
669 | type: 'number',
670 | tabIndex: '-1',
671 | onChange: this.controlsResize })
672 | ),
673 | _react2.default.createElement(
674 | 'label',
675 | null,
676 | this.props.heightLabel,
677 | _react2.default.createElement('input', {
678 | value: Math.round(height),
679 | type: 'number',
680 | name: 'height',
681 | tabIndex: '-1',
682 | onChange: this.controlsResize })
683 | )
684 | ),
685 | _react2.default.createElement('div', { className: 'DraggableResizable-top', style: topStyle }),
686 | _react2.default.createElement('div', { className: 'DraggableResizable-left', style: leftStyle }),
687 | _react2.default.createElement(
688 | 'div',
689 | { style: style, onMouseDown: this.startMove, onTouchStart: this.startMove },
690 | this.props.children,
691 | _react2.default.createElement('div', { className: 'resize-handle resize-handle-se',
692 | onMouseDown: this.startResize.bind(null, 'se'),
693 | onTouchStart: this.startResize.bind(null, 'se') }),
694 | _react2.default.createElement('div', { className: 'resize-handle resize-handle-ne',
695 | onMouseDown: this.startResize.bind(null, 'ne'),
696 | onTouchStart: this.startResize.bind(null, 'ne') }),
697 | _react2.default.createElement('div', { className: 'resize-handle resize-handle-sw',
698 | onMouseDown: this.startResize.bind(null, 'sw'),
699 | onTouchStart: this.startResize.bind(null, 'sw') }),
700 | _react2.default.createElement('div', { className: 'resize-handle resize-handle-nw',
701 | onMouseDown: this.startResize.bind(null, 'nw'),
702 | onTouchStart: this.startResize.bind(null, 'nw') })
703 | ),
704 | _react2.default.createElement('div', { className: 'DraggableResizable-right', style: rightStyle }),
705 | _react2.default.createElement('div', { className: 'DraggableResizable-bottom', style: bottomStyle })
706 | );
707 | }
708 | });
709 |
710 | /***/ },
711 | /* 3 */
712 | /***/ function(module, exports) {
713 |
714 |
715 | /**
716 | * Blob constructor.
717 | */
718 |
719 | var Blob = window.Blob;
720 |
721 | /**
722 | * ArrayBufferView support.
723 | */
724 |
725 | var hasArrayBufferView = new Blob([new Uint8Array(100)]).size == 100;
726 |
727 | /**
728 | * Return a `Blob` for the given data `uri`.
729 | *
730 | * @param {String} uri
731 | * @return {Blob}
732 | * @api public
733 | */
734 |
735 | module.exports = function(uri){
736 | var data = uri.split(',')[1];
737 | var bytes = atob(data);
738 | var buf = new ArrayBuffer(bytes.length);
739 | var arr = new Uint8Array(buf);
740 | for (var i = 0; i < bytes.length; i++) {
741 | arr[i] = bytes.charCodeAt(i);
742 | }
743 |
744 | if (!hasArrayBufferView) arr = buf;
745 | var blob = new Blob([arr], { type: mime(uri) });
746 | blob.slice = blob.slice || blob.webkitSlice;
747 | return blob;
748 | };
749 |
750 | /**
751 | * Return data uri mime type.
752 | */
753 |
754 | function mime(uri) {
755 | return uri.split(';')[0].slice(5);
756 | }
757 |
758 |
759 | /***/ },
760 | /* 4 */
761 | /***/ function(module, exports, __webpack_require__) {
762 |
763 | // style-loader: Adds some css to the DOM by adding a
13 |
14 |
15 |
16 |
17 |
18 |