114 |
115 |
118 | `;
119 |
120 | const domElement = document.getElementById('draggable');
121 | const draggables = document.getElementsByClassName('draggables');
122 | const draggableContainer = document.getElementById('container');
123 | const cloneableElement = document.getElementById('cloneable');
124 |
125 | const svgElement = document.getElementById('svg-draggable');
126 | const svgContainerElement = document.getElementById('svg-container');
127 |
128 | const defaultOptions = {
129 | axis: 'xy',
130 | cursorMove: 'auto',
131 | cursorRotate: 'auto',
132 | cursorResize: 'auto',
133 | rotationPoint: false,
134 | restrict: null,
135 | snap: { x: 10, y: 10, angle: 0.17453292519943295 },
136 | each: { move: false, resize: false, rotate: false },
137 | proportions: false,
138 | draggable: true,
139 | resizable: true,
140 | rotatable: true,
141 | scalable: false,
142 | applyTranslate: false,
143 | custom: null,
144 | rotatorAnchor: null,
145 | rotatorOffset: 50,
146 | showNormal: true,
147 | isGrouped: false,
148 | transformOrigin: false
149 | };
150 |
151 | const options = {
152 | rotationPoint: true,
153 | proportions: true,
154 | axis: 'x',
155 | each: {
156 | move: true,
157 | resize: true,
158 | rotate: true
159 | },
160 | snap: {
161 | x: 10,
162 | y: 20,
163 | angle: 30
164 | },
165 | cursorMove: 'move',
166 | cursorRotate: 'crosshair',
167 | cursorResize: 'pointer',
168 | draggable: true,
169 | resizable: true,
170 | rotatable: true,
171 | scalable: true,
172 | applyTranslate: false,
173 | custom: null,
174 | rotatorAnchor: 's',
175 | rotatorOffset: 20,
176 | showNormal: true
177 | };
178 |
179 | const createEMouseDown = () =>
180 | new MouseEvent('mousedown', {
181 | clientX: 10,
182 | clientY: 10,
183 | bubbles: true,
184 | cancelable: true,
185 | view: window
186 | });
187 |
188 | const createEMouseMove = () =>
189 | new MouseEvent('mousemove', {
190 | clientX: 150,
191 | clientY: 150,
192 | bubbles: true,
193 | cancelable: true,
194 | view: window
195 | });
196 |
197 | const createEMouseUp = () =>
198 | new MouseEvent('mouseup', {
199 | clientX: 150,
200 | clientY: 150,
201 | bubbles: true,
202 | cancelable: true,
203 | view: window
204 | });
205 |
206 | describe('Test subjx "clone" method', () => {
207 | it('init cloneable with defaults', () => {
208 | subjx(cloneableElement).clone();
209 | });
210 | });
211 |
212 | describe('Test subjx "drag" method', () => {
213 | it('init draggable with defaults', () => {
214 | const draggable = subjx(domElement).drag();
215 | expect(draggable.options).toEqual({
216 | ...defaultOptions,
217 | container: draggableContainer,
218 | controlsContainer: draggableContainer
219 | });
220 |
221 | draggable.disable();
222 | });
223 |
224 | it('test subjx api', () => {
225 | const draggable = subjx(draggables).drag({ each: { move: true } });
226 |
227 | expect(() => {
228 | draggable.fitControlsToSize();
229 | draggable.getBoundingRect();
230 | draggable.setCenterPoint();
231 | draggable.setTransformOrigin({ x: 0, y: 0 });
232 | draggable.getDimensions();
233 | ['t', 'b', 'l', 'r', 'v', 'h'].map(align => draggable.applyAlignment(align));
234 | }).not.toThrow();
235 | });
236 |
237 | it('init draggable with options', () => {
238 | const nextOptions = {
239 | ...options,
240 | container: '#container',
241 | restrict: '#container'
242 | };
243 | const $draggables = subjx(draggables).drag({
244 | ...nextOptions
245 | });
246 |
247 | expect($draggables.options).toMatchObject({
248 | ...nextOptions,
249 | restrict: draggableContainer,
250 | container: draggableContainer,
251 | controlsContainer: draggableContainer,
252 | snap: {
253 | ...nextOptions.snap,
254 | angle: 0.5235987755982988
255 | }
256 | });
257 |
258 | $draggables.disable();
259 | });
260 |
261 | it('test subjx hooks', () => {
262 | let init = false,
263 | move = false,
264 | resize = false,
265 | rotate = false,
266 | drop = false,
267 | destroy = false;
268 |
269 | const methods = {
270 | onInit() {
271 | init = true;
272 | },
273 | onMove() {
274 | move = true;
275 | },
276 | onResize() {
277 | resize = true;
278 | },
279 | onRotate() {
280 | rotate = true;
281 | },
282 | onDrop() {
283 | drop = true;
284 | },
285 | onDestroy() {
286 | destroy = true;
287 | }
288 | };
289 |
290 | const draggable = subjx(domElement).drag({ ...methods });
291 |
292 | // simulate move
293 | draggable.elements[0].dispatchEvent(createEMouseDown());
294 |
295 | let step = 0;
296 | while (step < 5) {
297 | document.dispatchEvent(createEMouseMove());
298 | jest.advanceTimersByTime(1001 / 60);
299 | step++;
300 | }
301 |
302 | document.dispatchEvent(createEMouseUp());
303 |
304 | // simulate resize
305 | draggable.storage.handles.tr.dispatchEvent(createEMouseDown());
306 |
307 | step = 0;
308 | while (step < 5) {
309 | document.dispatchEvent(createEMouseMove());
310 | jest.advanceTimersByTime(1001 / 60);
311 | step++;
312 | }
313 |
314 | document.dispatchEvent(createEMouseUp());
315 |
316 | // simulate rotate
317 | draggable.storage.handles.rotator.dispatchEvent(createEMouseDown());
318 |
319 | step = 0;
320 | while (step < 5) {
321 | document.dispatchEvent(createEMouseMove());
322 | jest.advanceTimersByTime(1001 / 60);
323 | step++;
324 | }
325 |
326 | document.dispatchEvent(createEMouseUp());
327 |
328 | draggable.disable();
329 |
330 | expect([
331 | init,
332 | move,
333 | rotate,
334 | resize,
335 | drop,
336 | destroy
337 | ].every((item) => item === true)).toEqual(true);
338 | });
339 |
340 | it('process move', () => {
341 | const $draggables = subjx(draggables).drag({ each: { move: true } });
342 |
343 | $draggables.elements[0].dispatchEvent(createEMouseDown());
344 |
345 | let step = 0;
346 | while (step < 5) {
347 | document.dispatchEvent(createEMouseMove());
348 | jest.advanceTimersByTime(1001 / 60);
349 | step++;
350 | }
351 |
352 | document.dispatchEvent(createEMouseUp());
353 |
354 | expect($draggables.storage).toMatchObject({
355 | clientX: 150,
356 | clientY: 150,
357 | relativeX: 10,
358 | relativeY: 10,
359 | isTarget: true
360 | });
361 |
362 | $draggables.disable();
363 | });
364 | });
365 |
366 | describe('Test svg subjx "drag" method', () => {
367 | it('init draggable with defaults', () => {
368 | const draggable = subjx(svgElement).drag();
369 | expect(draggable.options).toEqual({
370 | ...defaultOptions,
371 | container: svgContainerElement,
372 | controlsContainer: svgContainerElement
373 | });
374 |
375 | draggable.disable();
376 | });
377 |
378 | it('test subjx api', () => {
379 | const draggable = subjx(svgElement).drag({ each: { move: true } });
380 |
381 | expect(() => {
382 | draggable.fitControlsToSize();
383 | draggable.getBoundingRect(svgElement);
384 | draggable.setCenterPoint();
385 | draggable.setTransformOrigin({ x: 0, y: 0 });
386 | draggable.getDimensions();
387 | ['t', 'b', 'l', 'r', 'v', 'h'].map((align) => draggable.applyAlignment(align));
388 | }).not.toThrow();
389 | });
390 |
391 | it('init draggable with options', () => {
392 | const nextOptions = {
393 | container: '#svg-container',
394 | restrict: '#svg-container',
395 | ...options
396 | };
397 |
398 | const $draggable = subjx(svgElement).drag({
399 | ...nextOptions
400 | });
401 |
402 | expect($draggable.options).toMatchObject({
403 | ...nextOptions,
404 | restrict: svgContainerElement,
405 | container: svgContainerElement,
406 | controlsContainer: svgContainerElement,
407 | snap: {
408 | ...nextOptions.snap,
409 | angle: 0.5235987755982988
410 | }
411 | });
412 |
413 | $draggable.disable();
414 | });
415 |
416 | it('test subjx hooks', () => {
417 | let init = false,
418 | move = false,
419 | resize = false,
420 | rotate = false,
421 | drop = false,
422 | destroy = false;
423 |
424 | const methods = {
425 | onInit() {
426 | init = true;
427 | },
428 | onMove() {
429 | move = true;
430 | },
431 | onResize() {
432 | resize = true;
433 | },
434 | onRotate() {
435 | rotate = true;
436 | },
437 | onDrop() {
438 | drop = true;
439 | },
440 | onDestroy() {
441 | destroy = true;
442 | }
443 | };
444 |
445 | const draggable = subjx(svgElement).drag({ ...methods });
446 |
447 | // simulate move
448 | draggable.elements[0].dispatchEvent(createEMouseDown());
449 |
450 | let step = 0;
451 | while (step < 5) {
452 | document.dispatchEvent(createEMouseMove());
453 | jest.advanceTimersByTime(1001 / 60);
454 | step++;
455 | }
456 |
457 | document.dispatchEvent(createEMouseUp());
458 |
459 | // simulate resize
460 | draggable.storage.handles.tr.dispatchEvent(createEMouseDown());
461 |
462 | step = 0;
463 | while (step < 5) {
464 | document.dispatchEvent(createEMouseMove());
465 | jest.advanceTimersByTime(1001 / 60);
466 | step++;
467 | }
468 |
469 | document.dispatchEvent(createEMouseUp());
470 |
471 | // simulate rotate
472 | draggable.storage.handles.rotator.dispatchEvent(createEMouseDown());
473 |
474 | step = 0;
475 | while (step < 5) {
476 | document.dispatchEvent(createEMouseMove());
477 | jest.advanceTimersByTime(1001 / 60);
478 | step++;
479 | }
480 |
481 | document.dispatchEvent(createEMouseUp());
482 |
483 | draggable.disable();
484 |
485 | expect([
486 | init,
487 | move,
488 | rotate,
489 | resize,
490 | drop,
491 | destroy
492 | ].every((item) => item === true)).toEqual(true);
493 | });
494 |
495 | it('process move', () => {
496 | const $draggables = subjx(svgElement).drag({ each: { move: true } });
497 |
498 | $draggables.elements[0].dispatchEvent(createEMouseDown());
499 |
500 | let step = 0;
501 | while (step < 5) {
502 | document.dispatchEvent(createEMouseMove());
503 | jest.advanceTimersByTime(1001 / 60);
504 | step++;
505 | }
506 |
507 | document.dispatchEvent(createEMouseUp());
508 |
509 | expect($draggables.storage).toMatchObject({
510 | clientX: 150,
511 | clientY: 150,
512 | relativeX: 150,
513 | relativeY: 150,
514 | isTarget: true
515 | });
516 |
517 | $draggables.disable();
518 | });
519 | });
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
63 |
64 |
Demo
65 |
66 |
67 |
68 |
69 |
163 |
164 |
165 |
167 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
369 |
370 |
--------------------------------------------------------------------------------
/src/js/core/transform/svg/path.js:
--------------------------------------------------------------------------------
1 | import { warn } from './../../util/util';
2 | import { floatToFixed } from '../common';
3 |
4 | import {
5 | pointTo,
6 | cloneMatrix,
7 | normalizeString,
8 | sepRE,
9 | arrayToChunks
10 | } from './util';
11 |
12 | // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
13 | const dRE = /\s*([achlmqstvz])([^achlmqstvz]*)\s*/gi;
14 |
15 | const getCommandValuesLength = (cmd) => ([
16 | {
17 | size: 2,
18 | condition: ['M', 'm', 'L', 'l', 'T', 't'].includes(cmd)
19 | },
20 | {
21 | size: 1,
22 | condition: ['H', 'h', 'V', 'v'].includes(cmd)
23 | },
24 | {
25 | size: 6,
26 | condition: ['C', 'c'].includes(cmd)
27 | },
28 | {
29 | size: 4,
30 | condition: ['S', 's', 'Q', 'q'].includes(cmd)
31 | },
32 | {
33 | size: 7,
34 | condition: ['A', 'a'].includes(cmd)
35 | },
36 | {
37 | size: 1,
38 | condition: true
39 | }
40 | ].find(({ condition }) => !!condition));
41 |
42 | const parsePath = (path) => {
43 | let match = dRE.lastIndex = 0;
44 |
45 | const serialized = [];
46 |
47 | while ((match = dRE.exec(path))) {
48 | const [, cmd, params] = match;
49 | const upCmd = cmd.toUpperCase();
50 |
51 | const isRelative = cmd !== upCmd;
52 |
53 | const data = normalizeString(params);
54 |
55 | const values = data.trim().split(sepRE).map(val => {
56 | if (!isNaN(val)) {
57 | return Number(val);
58 | }
59 | });
60 |
61 | let firstCommand = false;
62 | const isMoveTo = upCmd === 'M';
63 |
64 | const { size: commandLength } = getCommandValuesLength(cmd);
65 |
66 | // split big command into multiple commands
67 | arrayToChunks(values, commandLength)
68 | .map(chunkedValues => {
69 | const shouldReplace = firstCommand && isMoveTo;
70 |
71 | firstCommand = firstCommand ? firstCommand : isMoveTo;
72 |
73 | return serialized.push({
74 | relative: isRelative,
75 | key: shouldReplace ? 'L' : upCmd,
76 | cmd: shouldReplace
77 | ? isRelative ? 'l' : 'L'
78 | : cmd,
79 | values: chunkedValues
80 | });
81 | });
82 | }
83 |
84 | return reducePathData(absolutizePathData(serialized));
85 | };
86 |
87 | export const movePath = (params) => {
88 | const {
89 | path,
90 | dx,
91 | dy
92 | } = params;
93 |
94 | try {
95 | const serialized = parsePath(path);
96 |
97 | let str = '';
98 | let space = ' ';
99 |
100 | let firstCommand = true;
101 |
102 | for (let i = 0, len = serialized.length; i < len; i++) {
103 | const item = serialized[i];
104 |
105 | const {
106 | values,
107 | key: cmd,
108 | relative
109 | } = item;
110 |
111 | const coordinates = [];
112 |
113 | switch (cmd) {
114 |
115 | case 'M': {
116 | for (let k = 0, len = values.length; k < len; k += 2) {
117 | let [x, y] = values.slice(k, k + 2);
118 |
119 | if (!(relative && !firstCommand)) {
120 | x += dx;
121 | y += dy;
122 | }
123 |
124 | coordinates.push(
125 | x,
126 | y
127 | );
128 |
129 | firstCommand = false;
130 | }
131 | break;
132 | }
133 | case 'A': {
134 | for (let k = 0, len = values.length; k < len; k += 7) {
135 | const set = values.slice(k, k + 7);
136 |
137 | if (!relative) {
138 | set[5] += dx;
139 | set[6] += dy;
140 | }
141 |
142 | coordinates.push(...set);
143 | }
144 | break;
145 | }
146 | case 'C': {
147 | for (let k = 0, len = values.length; k < len; k += 6) {
148 | const set = values.slice(k, k + 6);
149 |
150 | if (!relative) {
151 | set[0] += dx;
152 | set[1] += dy;
153 | set[2] += dx;
154 | set[3] += dy;
155 | set[4] += dx;
156 | set[5] += dy;
157 | }
158 |
159 | coordinates.push(...set);
160 | }
161 | break;
162 | }
163 | case 'H': {
164 | for (let k = 0, len = values.length; k < len; k += 1) {
165 | const set = values.slice(k, k + 1);
166 |
167 | if (!relative) {
168 | set[0] += dx;
169 | }
170 |
171 | coordinates.push(set[0]);
172 | }
173 |
174 | break;
175 | }
176 | case 'V': {
177 | for (let k = 0, len = values.length; k < len; k += 1) {
178 | const set = values.slice(k, k + 1);
179 |
180 | if (!relative) {
181 | set[0] += dy;
182 | }
183 | coordinates.push(set[0]);
184 | }
185 |
186 | break;
187 | }
188 | case 'L':
189 | case 'T': {
190 | for (let k = 0, len = values.length; k < len; k += 2) {
191 | let [x, y] = values.slice(k, k + 2);
192 |
193 | if (!relative) {
194 | x += dx;
195 | y += dy;
196 | }
197 |
198 | coordinates.push(
199 | x,
200 | y
201 | );
202 | }
203 | break;
204 | }
205 | case 'Q':
206 | case 'S': {
207 | for (let k = 0, len = values.length; k < len; k += 4) {
208 | let [x1, y1, x2, y2] = values.slice(k, k + 4);
209 |
210 | if (!relative) {
211 | x1 += dx;
212 | y1 += dy;
213 | x2 += dx;
214 | y2 += dy;
215 | }
216 |
217 | coordinates.push(
218 | x1,
219 | y1,
220 | x2,
221 | y2
222 | );
223 | }
224 | break;
225 | }
226 | case 'Z': {
227 | values[0] = '';
228 | space = '';
229 | break;
230 | }
231 |
232 | }
233 |
234 | str += cmd + coordinates.join(',') + space;
235 | }
236 |
237 | return str;
238 | } catch (err) {
239 | warn('Path parsing error: ' + err);
240 | }
241 | };
242 |
243 | export const resizePath = (params) => {
244 | const {
245 | path,
246 | localCTM
247 | } = params;
248 |
249 | try {
250 | const serialized = parsePath(path);
251 |
252 | let str = '';
253 | let space = ' ';
254 |
255 | const res = [];
256 |
257 | let firstCommand = true;
258 |
259 | for (let i = 0, len = serialized.length; i < len; i++) {
260 | const item = serialized[i];
261 |
262 | const {
263 | values,
264 | key: cmd,
265 | relative = false
266 | } = item;
267 |
268 | switch (cmd) {
269 |
270 | case 'A': {
271 | // A rx ry x-axis-rotation large-arc-flag sweep-flag x y
272 | const coordinates = [];
273 |
274 | const mtrx = cloneMatrix(localCTM);
275 |
276 | if (relative) {
277 | mtrx.e = mtrx.f = 0;
278 | }
279 |
280 | for (let k = 0, len = values.length; k < len; k += 7) {
281 | const [rx, ry, xAxisRot, largeArcFlag, sweepFlag, x, y] =
282 | values.slice(k, k + 7);
283 |
284 | const {
285 | x: resX,
286 | y: resY
287 | } = pointTo(
288 | mtrx,
289 | x,
290 | y
291 | );
292 |
293 | coordinates.push(
294 | floatToFixed(resX),
295 | floatToFixed(resY)
296 | );
297 |
298 | mtrx.e = mtrx.f = 0;
299 |
300 | const {
301 | x: newRx,
302 | y: newRy
303 | } = pointTo(
304 | mtrx,
305 | rx,
306 | ry
307 | );
308 |
309 | coordinates.unshift(
310 | floatToFixed(newRx),
311 | floatToFixed(newRy),
312 | xAxisRot,
313 | largeArcFlag,
314 | sweepFlag
315 | );
316 | }
317 |
318 | res.push(coordinates);
319 | break;
320 | }
321 | case 'C': {
322 | // C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)
323 | const coordinates = [];
324 |
325 | const mtrx = cloneMatrix(localCTM);
326 |
327 | if (relative) {
328 | mtrx.e = mtrx.f = 0;
329 | }
330 |
331 | for (let k = 0, len = values.length; k < len; k += 6) {
332 | const [x1, y1, x2, y2, x, y] = values.slice(k, k + 6);
333 |
334 | const {
335 | x: resX1,
336 | y: resY1
337 | } = pointTo(
338 | mtrx,
339 | x1,
340 | y1
341 | );
342 |
343 | const {
344 | x: resX2,
345 | y: resY2
346 | } = pointTo(
347 | mtrx,
348 | x2,
349 | y2
350 | );
351 |
352 | const {
353 | x: resX,
354 | y: resY
355 | } = pointTo(
356 | mtrx,
357 | x,
358 | y
359 | );
360 |
361 | coordinates.push(
362 | floatToFixed(resX1),
363 | floatToFixed(resY1),
364 | floatToFixed(resX2),
365 | floatToFixed(resY2),
366 | floatToFixed(resX),
367 | floatToFixed(resY)
368 | );
369 | }
370 |
371 | res.push(coordinates);
372 | break;
373 | }
374 | // this command makes impossible free transform within group
375 | // it will be converted to L
376 | case 'H': {
377 | // H x (or h dx)
378 | const coordinates = [];
379 |
380 | const mtrx = cloneMatrix(localCTM);
381 |
382 | if (relative) {
383 | mtrx.e = mtrx.f = 0;
384 | }
385 |
386 | for (let k = 0, len = values.length; k < len; k += 1) {
387 | const [x] = values.slice(k, k + 1);
388 |
389 | const {
390 | x: resX
391 | } = pointTo(
392 | mtrx,
393 | x,
394 | 0
395 | );
396 |
397 | coordinates.push(
398 | floatToFixed(resX)
399 | );
400 | }
401 |
402 | res.push(coordinates);
403 | break;
404 | }
405 | // this command makes impossible free transform within group
406 | // it will be converted to L
407 | case 'V': {
408 | // V y (or v dy)
409 | const coordinates = [];
410 |
411 | const mtrx = cloneMatrix(localCTM);
412 |
413 | if (relative) {
414 | mtrx.e = mtrx.f = 0;
415 | }
416 |
417 | for (let k = 0, len = values.length; k < len; k += 1) {
418 | const [y] = values.slice(k, k + 1);
419 |
420 | const {
421 | y: resY
422 | } = pointTo(
423 | mtrx,
424 | 0,
425 | y
426 | );
427 |
428 | coordinates.push(
429 | floatToFixed(resY)
430 | );
431 | }
432 |
433 | res.push(coordinates);
434 | break;
435 | }
436 | case 'T':
437 | case 'L': {
438 | // T x y (or t dx dy)
439 | // L x y (or l dx dy)
440 | const coordinates = [];
441 |
442 | const mtrx = cloneMatrix(localCTM);
443 |
444 | if (relative) {
445 | mtrx.e = mtrx.f = 0;
446 | }
447 |
448 | for (let k = 0, len = values.length; k < len; k += 2) {
449 | const [x, y] = values.slice(k, k + 2);
450 |
451 | const {
452 | x: resX,
453 | y: resY
454 | } = pointTo(
455 | mtrx,
456 | x,
457 | y
458 | );
459 |
460 | coordinates.push(
461 | floatToFixed(resX),
462 | floatToFixed(resY)
463 | );
464 | }
465 |
466 | res.push(coordinates);
467 | break;
468 | }
469 | case 'M': {
470 | // M x y (or dx dy)
471 | const coordinates = [];
472 |
473 | const mtrx = cloneMatrix(localCTM);
474 |
475 | if (relative && !firstCommand) {
476 | mtrx.e = mtrx.f = 0;
477 | }
478 |
479 | for (let k = 0, len = values.length; k < len; k += 2) {
480 | const [x, y] = values.slice(k, k + 2);
481 |
482 | const {
483 | x: resX,
484 | y: resY
485 | } = pointTo(
486 | mtrx,
487 | x,
488 | y
489 | );
490 |
491 | coordinates.push(
492 | floatToFixed(resX),
493 | floatToFixed(resY)
494 | );
495 |
496 | firstCommand = false;
497 | }
498 |
499 | res.push(coordinates);
500 | break;
501 | }
502 | case 'Q': {
503 | // Q x1 y1, x y (or q dx1 dy1, dx dy)
504 | const coordinates = [];
505 |
506 | const mtrx = cloneMatrix(localCTM);
507 |
508 | if (relative) {
509 | mtrx.e = mtrx.f = 0;
510 | }
511 |
512 | for (let k = 0, len = values.length; k < len; k += 4) {
513 | const [x1, y1, x, y] = values.slice(k, k + 4);
514 |
515 | const {
516 | x: resX1,
517 | y: resY1
518 | } = pointTo(
519 | mtrx,
520 | x1,
521 | y1
522 | );
523 |
524 | const {
525 | x: resX,
526 | y: resY
527 | } = pointTo(
528 | mtrx,
529 | x,
530 | y
531 | );
532 |
533 | coordinates.push(
534 | floatToFixed(resX1),
535 | floatToFixed(resY1),
536 | floatToFixed(resX),
537 | floatToFixed(resY)
538 | );
539 | }
540 |
541 | res.push(coordinates);
542 | break;
543 | }
544 | case 'S': {
545 | // S x2 y2, x y (or s dx2 dy2, dx dy)
546 | const coordinates = [];
547 |
548 | const mtrx = cloneMatrix(localCTM);
549 |
550 | if (relative) {
551 | mtrx.e = mtrx.f = 0;
552 | }
553 |
554 | for (let k = 0, len = values.length; k < len; k += 4) {
555 | const [x2, y2, x, y] = values.slice(k, k + 4);
556 |
557 | const {
558 | x: resX2,
559 | y: resY2
560 | } = pointTo(
561 | mtrx,
562 | x2,
563 | y2
564 | );
565 |
566 | const {
567 | x: resX,
568 | y: resY
569 | } = pointTo(
570 | mtrx,
571 | x,
572 | y
573 | );
574 |
575 | coordinates.push(
576 | floatToFixed(resX2),
577 | floatToFixed(resY2),
578 | floatToFixed(resX),
579 | floatToFixed(resY)
580 | );
581 | }
582 |
583 | res.push(coordinates);
584 | break;
585 | }
586 | case 'Z': {
587 | res.push(['']);
588 | space = '';
589 | break;
590 | }
591 |
592 | }
593 |
594 | str += item.key + res[i].join(',') + space;
595 | }
596 |
597 | return str.trim();
598 | } catch (err) {
599 | warn('Path parsing error: ' + err);
600 | }
601 | };
602 |
603 | const absolutizePathData = (pathData) => {
604 | let currentX = null,
605 | currentY = null,
606 | subpathX = null,
607 | subpathY = null;
608 |
609 | return pathData.reduce((absolutizedPathData, seg) => {
610 | const { cmd, values } = seg;
611 |
612 | let nextSeg;
613 |
614 | switch (cmd) {
615 |
616 | case 'M': {
617 | const [x, y] = values;
618 |
619 | nextSeg = { key: 'M', values: [x, y] };
620 |
621 | subpathX = x;
622 | subpathY = y;
623 |
624 | currentX = x;
625 | currentY = y;
626 | break;
627 | }
628 |
629 | case 'm': {
630 | const [x, y] = values;
631 |
632 | const nextX = currentX + x;
633 | const nextY = currentY + y;
634 |
635 | nextSeg = { key: 'M', values: [nextX, nextY] };
636 |
637 | subpathX = nextX;
638 | subpathY = nextY;
639 |
640 | currentX = nextX;
641 | currentY = nextY;
642 | break;
643 | }
644 |
645 | case 'L': {
646 | const [x, y] = values;
647 |
648 | nextSeg = { key: 'L', values: [x, y] };
649 |
650 | currentX = x;
651 | currentY = y;
652 | break;
653 | }
654 |
655 | case 'l': {
656 | const [x, y] = values;
657 | const nextX = currentX + x;
658 | const nextY = currentY + y;
659 |
660 | nextSeg = { key: 'L', values: [nextX, nextY] };
661 |
662 | currentX = nextX;
663 | currentY = nextY;
664 | break;
665 | }
666 |
667 | case 'C': {
668 | const [x1, y1, x2, y2, x, y] = values;
669 |
670 | nextSeg = { key: 'C', values: [x1, y1, x2, y2, x, y] };
671 |
672 | currentX = x;
673 | currentY = y;
674 | break;
675 | }
676 |
677 | case 'c': {
678 | const [x1, y1, x2, y2, x, y] = values;
679 |
680 | const nextValues = [
681 | currentX + x1,
682 | currentY + y1,
683 | currentX + x2,
684 | currentY + y2,
685 | currentX + x,
686 | currentY + y
687 | ];
688 |
689 | nextSeg = { key: 'C', values: [...nextValues] };
690 |
691 | currentX = nextValues[4];
692 | currentY = nextValues[5];
693 | break;
694 | }
695 |
696 | case 'Q': {
697 | const [x1, y1, x, y] = values;
698 |
699 | nextSeg = { key: 'Q', values: [x1, y1, x, y] };
700 |
701 | currentX = x;
702 | currentY = y;
703 | break;
704 | }
705 |
706 | case 'q': {
707 | const [x1, y1, x, y] = values;
708 |
709 | const nextValues = [
710 | currentX + x1,
711 | currentY + y1,
712 | currentX + x,
713 | currentY + y
714 | ];
715 |
716 | absolutizedPathData.push({ key: 'Q', values: [...nextValues] });
717 |
718 | currentX = nextValues[2];
719 | currentY = nextValues[3];
720 | break;
721 | }
722 |
723 | case 'A': {
724 | const [r1, r2, angle, largeArcFlag, sweepFlag, x, y] = values;
725 |
726 | nextSeg = {
727 | key: 'A',
728 | values: [r1, r2, angle, largeArcFlag, sweepFlag, x, y]
729 | };
730 |
731 | currentX = x;
732 | currentY = y;
733 | break;
734 | }
735 |
736 | case 'a': {
737 | const [r1, r2, angle, largeArcFlag, sweepFlag, x, y] = values;
738 | const nextX = currentX + x;
739 | const nextY = currentY + y;
740 |
741 | nextSeg = {
742 | key: 'A',
743 | values: [r1, r2, angle, largeArcFlag, sweepFlag, nextX, nextY]
744 | };
745 |
746 | currentX = nextX;
747 | currentY = nextY;
748 | break;
749 | }
750 |
751 | case 'H': {
752 | const [x] = values;
753 | nextSeg = { key: 'H', values: [x] };
754 | currentX = x;
755 | break;
756 | }
757 |
758 | case 'h': {
759 | const [x] = values;
760 | const nextX = currentX + x;
761 |
762 | nextSeg = { key: 'H', values: [nextX] };
763 | currentX = nextX;
764 | break;
765 | }
766 |
767 | case 'V': {
768 | const [y] = values;
769 | nextSeg = { key: 'V', values: [y] };
770 | currentY = y;
771 | break;
772 | }
773 |
774 | case 'v': {
775 | const [y] = values;
776 | const nextY = currentY + y;
777 | nextSeg = { key: 'V', values: [nextY] };
778 | currentY = nextY;
779 | break;
780 | }
781 | case 'S': {
782 | const [x2, y2, x, y] = values;
783 |
784 | nextSeg = { key: 'S', values: [x2, y2, x, y] };
785 |
786 | currentX = x;
787 | currentY = y;
788 | break;
789 | }
790 |
791 | case 's': {
792 | const [x2, y2, x, y] = values;
793 |
794 | const nextValues = [
795 | currentX + x2,
796 | currentY + y2,
797 | currentX + x,
798 | currentY + y
799 | ];
800 |
801 | nextSeg = { key: 'S', values: [...nextValues] };
802 |
803 | currentX = nextValues[2];
804 | currentY = nextValues[3];
805 | break;
806 | }
807 |
808 | case 'T': {
809 | const [x, y] = values;
810 |
811 | nextSeg = { key: 'T', values: [x, y] };
812 |
813 | currentX = x;
814 | currentY = y;
815 | break;
816 | }
817 |
818 | case 't': {
819 | const [x, y] = values;
820 | const nextX = currentX + x;
821 | const nextY = currentY + y;
822 |
823 | nextSeg = { key: 'T', values: [nextX, nextY] };
824 |
825 | currentX = nextX;
826 | currentY = nextY;
827 | break;
828 | }
829 |
830 | case 'Z':
831 | case 'z': {
832 | nextSeg = { key: 'Z', values: [] };
833 |
834 | currentX = subpathX;
835 | currentY = subpathY;
836 | break;
837 | }
838 |
839 | }
840 |
841 | return [...absolutizedPathData, nextSeg];
842 | }, []);
843 | };
844 |
845 | const reducePathData = (pathData) => {
846 | let lastType = null;
847 |
848 | let lastControlX = null;
849 | let lastControlY = null;
850 |
851 | let currentX = null;
852 | let currentY = null;
853 |
854 | let subpathX = null;
855 | let subpathY = null;
856 |
857 | return pathData.reduce((reducedPathData, seg) => {
858 | const { key, values } = seg;
859 |
860 | let nextSeg;
861 |
862 | switch (key) {
863 |
864 | case 'M': {
865 | const [x, y] = values;
866 |
867 | nextSeg = [{ key: 'M', values: [x, y] }];
868 |
869 | subpathX = x;
870 | subpathY = y;
871 |
872 | currentX = x;
873 | currentY = y;
874 | break;
875 | }
876 |
877 | case 'C': {
878 | const [x1, y1, x2, y2, x, y] = values;
879 |
880 | nextSeg = [{ key: 'C', values: [x1, y1, x2, y2, x, y] }];
881 |
882 | lastControlX = x2;
883 | lastControlY = y2;
884 |
885 | currentX = x;
886 | currentY = y;
887 | break;
888 | }
889 |
890 | case 'L': {
891 | const [x, y] = values;
892 |
893 | nextSeg = [{ key: 'L', values: [x, y] }];
894 |
895 | currentX = x;
896 | currentY = y;
897 | break;
898 | }
899 |
900 | case 'H': {
901 | const [x] = values;
902 |
903 | nextSeg = [{ key: 'L', values: [x, currentY] }];
904 |
905 | currentX = x;
906 | break;
907 | }
908 |
909 | case 'V': {
910 | const [y] = values;
911 |
912 | nextSeg = [{ key: 'L', values: [currentX, y] }];
913 |
914 | currentY = y;
915 | break;
916 | }
917 |
918 | case 'S': {
919 | const [x2, y2, x, y] = values;
920 |
921 | let cx1, cy1;
922 |
923 | if (lastType === 'C' || lastType === 'S') {
924 | cx1 = currentX + (currentX - lastControlX);
925 | cy1 = currentY + (currentY - lastControlY);
926 | } else {
927 | cx1 = currentX;
928 | cy1 = currentY;
929 | }
930 |
931 | nextSeg = [{ key: 'C', values: [cx1, cy1, x2, y2, x, y] }];
932 |
933 | lastControlX = x2;
934 | lastControlY = y2;
935 |
936 | currentX = x;
937 | currentY = y;
938 | break;
939 | }
940 |
941 | case 'T': {
942 | const [x, y] = values;
943 |
944 | let x1, y1;
945 |
946 | if (lastType === 'Q' || lastType === 'T') {
947 | x1 = currentX + (currentX - lastControlX);
948 | y1 = currentY + (currentY - lastControlY);
949 | } else {
950 | x1 = currentX;
951 | y1 = currentY;
952 | }
953 |
954 | const cx1 = currentX + 2 * (x1 - currentX) / 3;
955 | const cy1 = currentY + 2 * (y1 - currentY) / 3;
956 | const cx2 = x + 2 * (x1 - x) / 3;
957 | const cy2 = y + 2 * (y1 - y) / 3;
958 |
959 | nextSeg = [{ key: 'C', values: [cx1, cy1, cx2, cy2, x, y] }];
960 |
961 | lastControlX = x1;
962 | lastControlY = y1;
963 |
964 | currentX = x;
965 | currentY = y;
966 | break;
967 | }
968 |
969 | case 'Q': {
970 | const [x1, y1, x, y] = values;
971 |
972 | const cx1 = currentX + 2 * (x1 - currentX) / 3;
973 | const cy1 = currentY + 2 * (y1 - currentY) / 3;
974 | const cx2 = x + 2 * (x1 - x) / 3;
975 | const cy2 = y + 2 * (y1 - y) / 3;
976 |
977 | nextSeg = [{ key: 'C', values: [cx1, cy1, cx2, cy2, x, y] }];
978 |
979 | lastControlX = x1;
980 | lastControlY = y1;
981 |
982 | currentX = x;
983 | currentY = y;
984 | break;
985 | }
986 |
987 | case 'A': {
988 | const [r1, r2, angle, largeArcFlag, sweepFlag, x, y] = values;
989 |
990 | if (r1 === 0 || r2 === 0) {
991 | nextSeg = [{ key: 'C', values: [currentX, currentY, x, y, x, y] }];
992 |
993 | currentX = x;
994 | currentY = y;
995 | } else {
996 | if (currentX !== x || currentY !== y) {
997 | const curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
998 |
999 | nextSeg = curves.map(curve => ({ key: 'C', values: curve }));
1000 |
1001 | currentX = x;
1002 | currentY = y;
1003 | }
1004 | }
1005 | break;
1006 | }
1007 |
1008 | case 'Z': {
1009 | nextSeg = [seg];
1010 |
1011 | currentX = subpathX;
1012 | currentY = subpathY;
1013 | break;
1014 | }
1015 |
1016 | }
1017 |
1018 | lastType = key;
1019 |
1020 | return [...reducedPathData, ...nextSeg];
1021 | }, []);
1022 | };
1023 |
1024 | // - a2c() by Dmitry Baranovskiy (MIT License)
1025 | // https://github.com/DmitryBaranovskiy/raphael/blob/v2.1.1/raphael.js#L2216
1026 | const arcToCubicCurves = (x1, y1, x2, y2, rx, ry, xAxisRot, largeArcFlag, sweepFlag, recursive) => {
1027 | const degToRad = deg => (Math.PI * deg) / 180;
1028 |
1029 | const rotate = (x, y, rad) => ({
1030 | x: x * Math.cos(rad) - y * Math.sin(rad),
1031 | y: x * Math.sin(rad) + y * Math.cos(rad)
1032 | });
1033 |
1034 | const angleRad = degToRad(xAxisRot);
1035 | let params = [];
1036 | let f1, f2, cx, cy;
1037 |
1038 | if (recursive) {
1039 | f1 = recursive[0];
1040 | f2 = recursive[1];
1041 | cx = recursive[2];
1042 | cy = recursive[3];
1043 | } else {
1044 | const p1 = rotate(x1, y1, -angleRad);
1045 | x1 = p1.x;
1046 | y1 = p1.y;
1047 |
1048 | const p2 = rotate(x2, y2, -angleRad);
1049 | x2 = p2.x;
1050 | y2 = p2.y;
1051 |
1052 | const x = (x1 - x2) / 2;
1053 | const y = (y1 - y2) / 2;
1054 | let h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
1055 |
1056 | if (h > 1) {
1057 | h = Math.sqrt(h);
1058 | rx = h * rx;
1059 | ry = h * ry;
1060 | }
1061 |
1062 | let sign = largeArcFlag === sweepFlag ? -1 : 1;
1063 |
1064 | const r1Pow = rx * rx;
1065 | const r2Pow = ry * ry;
1066 |
1067 | const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
1068 | const right = r1Pow * y * y + r2Pow * x * x;
1069 |
1070 | let k = sign * Math.sqrt(Math.abs(left / right));
1071 |
1072 | cx = k * rx * y / ry + (x1 + x2) / 2;
1073 | cy = k * -ry * x / rx + (y1 + y2) / 2;
1074 |
1075 | f1 = Math.asin(parseFloat(((y1 - cy) / ry).toFixed(9)));
1076 | f2 = Math.asin(parseFloat(((y2 - cy) / ry).toFixed(9)));
1077 |
1078 | if (x1 < cx) {
1079 | f1 = Math.PI - f1;
1080 | }
1081 |
1082 | if (x2 < cx) {
1083 | f2 = Math.PI - f2;
1084 | }
1085 |
1086 | if (f1 < 0) {
1087 | f1 = Math.PI * 2 + f1;
1088 | }
1089 |
1090 | if (f2 < 0) {
1091 | f2 = Math.PI * 2 + f2;
1092 | }
1093 |
1094 | if (sweepFlag && f1 > f2) {
1095 | f1 = f1 - Math.PI * 2;
1096 | }
1097 |
1098 | if (!sweepFlag && f2 > f1) {
1099 | f2 = f2 - Math.PI * 2;
1100 | }
1101 | }
1102 |
1103 | let df = f2 - f1;
1104 |
1105 | if (Math.abs(df) > (Math.PI * 120 / 180)) {
1106 | let f2old = f2;
1107 | let x2old = x2;
1108 | let y2old = y2;
1109 |
1110 | const ratio = sweepFlag && f2 > f1 ? 1 : -1;
1111 |
1112 | f2 = f1 + (Math.PI * 120 / 180) * ratio;
1113 |
1114 | x2 = cx + rx * Math.cos(f2);
1115 | y2 = cy + ry * Math.sin(f2);
1116 | params = arcToCubicCurves(x2, y2, x2old, y2old, rx, ry, xAxisRot, 0, sweepFlag, [f2, f2old, cx, cy]);
1117 | }
1118 |
1119 | df = f2 - f1;
1120 |
1121 | let c1 = Math.cos(f1);
1122 | let s1 = Math.sin(f1);
1123 | let c2 = Math.cos(f2);
1124 | let s2 = Math.sin(f2);
1125 | let t = Math.tan(df / 4);
1126 | let hx = 4 / 3 * rx * t;
1127 | let hy = 4 / 3 * ry * t;
1128 |
1129 | let m1 = [x1, y1];
1130 | let m2 = [x1 + hx * s1, y1 - hy * c1];
1131 | let m3 = [x2 + hx * s2, y2 - hy * c2];
1132 | let m4 = [x2, y2];
1133 |
1134 | m2[0] = 2 * m1[0] - m2[0];
1135 | m2[1] = 2 * m1[1] - m2[1];
1136 |
1137 | if (recursive) {
1138 | return [m2, m3, m4, ...params];
1139 | } else {
1140 | params = [m2, m3, m4, ...params].join().split(',');
1141 |
1142 | const curves = [];
1143 | let curveParams = [];
1144 |
1145 | params.forEach((_, i) => {
1146 | if (i % 2) {
1147 | curveParams.push(rotate(params[i - 1], params[i], angleRad).y);
1148 | } else {
1149 | curveParams.push(rotate(params[i], params[i + 1], angleRad).x);
1150 | }
1151 |
1152 | if (curveParams.length === 6) {
1153 | curves.push(curveParams);
1154 | curveParams = [];
1155 | }
1156 | });
1157 |
1158 | return curves;
1159 | }
1160 | };
--------------------------------------------------------------------------------
/src/js/core/transform/Transformable.js:
--------------------------------------------------------------------------------
1 | import { helper } from '../Helper';
2 | import SubjectModel from '../SubjectModel';
3 | import { getMinMaxOfArray, snapToGrid, RAD } from './common';
4 |
5 | import {
6 | LIB_CLASS_PREFIX,
7 | NOTIFIER_CONSTANTS,
8 | EVENT_EMITTER_CONSTANTS,
9 | TRANSFORM_HANDLES_CONSTANTS,
10 | CLIENT_EVENTS_CONSTANTS
11 | } from '../consts';
12 |
13 | import {
14 | requestAnimFrame,
15 | cancelAnimFrame,
16 | isDef,
17 | isUndef,
18 | createMethod,
19 | noop,
20 | warn
21 | } from '../util/util';
22 |
23 | import {
24 | addClass,
25 | removeClass
26 | } from '../util/css-util';
27 |
28 | const {
29 | NOTIFIER_EVENTS,
30 | ON_GETSTATE,
31 | ON_APPLY,
32 | ON_MOVE,
33 | ON_RESIZE,
34 | ON_ROTATE
35 | } = NOTIFIER_CONSTANTS;
36 |
37 | const {
38 | EMITTER_EVENTS,
39 | E_DRAG_START,
40 | E_DRAG,
41 | E_DRAG_END,
42 | E_RESIZE_START,
43 | E_RESIZE,
44 | E_RESIZE_END,
45 | E_ROTATE_START,
46 | E_ROTATE,
47 | E_ROTATE_END,
48 | E_SET_POINT,
49 | E_SET_POINT_END
50 | } = EVENT_EMITTER_CONSTANTS;
51 |
52 | const { TRANSFORM_HANDLES_KEYS, TRANSFORM_EDGES_KEYS } = TRANSFORM_HANDLES_CONSTANTS;
53 | const {
54 | E_MOUSEDOWN,
55 | E_TOUCHSTART,
56 | E_MOUSEMOVE,
57 | E_MOUSEUP,
58 | E_TOUCHMOVE,
59 | E_TOUCHEND
60 | } = CLIENT_EVENTS_CONSTANTS;
61 |
62 | const {
63 | TOP_LEFT,
64 | TOP_CENTER,
65 | TOP_RIGHT,
66 | BOTTOM_LEFT,
67 | BOTTOM_RIGHT,
68 | BOTTOM_CENTER,
69 | MIDDLE_LEFT,
70 | MIDDLE_RIGHT
71 | } = TRANSFORM_HANDLES_KEYS;
72 |
73 | const {
74 | TOP_EDGE,
75 | BOTTOM_EDGE,
76 | LEFT_EDGE,
77 | RIGHT_EDGE
78 | } = TRANSFORM_EDGES_KEYS;
79 |
80 | const { keys, values } = Object;
81 |
82 | export default class Transformable extends SubjectModel {
83 |
84 | constructor(elements, options, observable) {
85 | super(elements);
86 | if (this.constructor === Transformable) {
87 | throw new TypeError('Cannot construct Transformable instances directly');
88 | }
89 | this.observable = observable;
90 |
91 | EMITTER_EVENTS.forEach(eventName => this.eventDispatcher.registerEvent(eventName));
92 | super.enable(options);
93 | }
94 |
95 | _cursorPoint() {
96 | throw Error(`'_cursorPoint()' method not implemented`);
97 | }
98 |
99 | _rotate({ element, radians, ...rest }) {
100 | const resultMtrx = this._processRotate(element, radians);
101 | const finalArgs = {
102 | transform: resultMtrx,
103 | delta: radians,
104 | ...rest
105 | };
106 | this.proxyMethods.onRotate.call(this, finalArgs);
107 | super._emitEvent(E_ROTATE, finalArgs);
108 | }
109 |
110 | _resize({ element, dx, dy, ...rest }) {
111 | const finalValues = this._processResize(element, { dx, dy });
112 | const finalArgs = {
113 | ...finalValues,
114 | dx,
115 | dy,
116 | ...rest
117 | };
118 | this.proxyMethods.onResize.call(this, finalArgs);
119 | super._emitEvent(E_RESIZE, finalArgs);
120 | }
121 |
122 | _processOptions(options = {}) {
123 | const { elements } = this;
124 |
125 | [...elements].map(element => addClass(element, `${LIB_CLASS_PREFIX}drag`));
126 |
127 | const {
128 | each = {
129 | move: false,
130 | resize: false,
131 | rotate: false
132 | },
133 | snap = {
134 | x: 10,
135 | y: 10,
136 | angle: 10
137 | },
138 | axis = 'xy',
139 | cursorMove = 'auto',
140 | cursorResize = 'auto',
141 | cursorRotate = 'auto',
142 | rotationPoint = false,
143 | transformOrigin = false,
144 | restrict,
145 | draggable = true,
146 | resizable = true,
147 | rotatable = true,
148 | scalable = false,
149 | applyTranslate = false,
150 | onInit = noop,
151 | onDrop = noop,
152 | onMove = noop,
153 | onResize = noop,
154 | onRotate = noop,
155 | onDestroy = noop,
156 | container = elements[0].parentNode,
157 | controlsContainer = container,
158 | proportions = false,
159 | rotatorAnchor = null,
160 | rotatorOffset = 50,
161 | showNormal = true,
162 | custom
163 | } = options;
164 |
165 | this.options = {
166 | axis,
167 | cursorMove,
168 | cursorRotate,
169 | cursorResize,
170 | rotationPoint,
171 | transformOrigin: transformOrigin || rotationPoint,
172 | restrict: restrict
173 | ? helper(restrict)[0] || document.body
174 | : null,
175 | container: helper(container)[0],
176 | controlsContainer: helper(controlsContainer)[0],
177 | snap: {
178 | ...snap,
179 | angle: snap.angle * RAD
180 | },
181 | each,
182 | proportions,
183 | draggable,
184 | resizable,
185 | rotatable,
186 | scalable,
187 | applyTranslate,
188 | custom: (typeof custom === 'object' && custom) || null,
189 | rotatorAnchor,
190 | rotatorOffset,
191 | showNormal,
192 | isGrouped: elements.length > 1
193 | };
194 |
195 | this.proxyMethods = {
196 | onInit: createMethod(onInit),
197 | onDrop: createMethod(onDrop),
198 | onMove: createMethod(onMove),
199 | onResize: createMethod(onResize),
200 | onRotate: createMethod(onRotate),
201 | onDestroy: createMethod(onDestroy)
202 | };
203 |
204 | this.subscribe(each);
205 | }
206 |
207 | _animate() {
208 | const self = this;
209 | const {
210 | observable,
211 | storage,
212 | options,
213 | elements
214 | } = self;
215 |
216 | if (isUndef(storage)) return;
217 |
218 | storage.frame = requestAnimFrame(self._animate);
219 |
220 | if (!storage.doDraw) return;
221 | storage.doDraw = false;
222 |
223 | let {
224 | dox,
225 | doy,
226 | clientX,
227 | clientY,
228 | relativeX,
229 | relativeY,
230 | doDrag,
231 | doResize,
232 | doRotate,
233 | doSetCenter,
234 | revX,
235 | revY,
236 | mouseEvent,
237 | data
238 | } = storage;
239 |
240 | const {
241 | snap,
242 | each: {
243 | move: moveEach,
244 | resize: resizeEach,
245 | rotate: rotateEach
246 | },
247 | draggable,
248 | resizable,
249 | rotatable,
250 | isGrouped,
251 | restrict
252 | } = options;
253 |
254 | if (doResize && resizable) {
255 | const distX = snapToGrid(clientX - relativeX, snap.x);
256 | const distY = snapToGrid(clientY - relativeY, snap.y);
257 |
258 | const {
259 | cached,
260 | cached: {
261 | dist: {
262 | dx: prevDx = distX,
263 | dy: prevDy = distY
264 | } = {}
265 | } = {}
266 | } = storage;
267 |
268 | const args = {
269 | dx: distX,
270 | dy: distY,
271 | clientX,
272 | clientY,
273 | mouseEvent
274 | };
275 |
276 | const { x: restX, y: restY } = restrict
277 | ? elements.reduce((res, element) => {
278 | const {
279 | transform: {
280 | // scX,
281 | // scY,
282 | ctm
283 | }
284 | } = data.get(element);
285 |
286 | const { x, y } = !isGrouped
287 | ? this._pointToTransform(
288 | {
289 | x: distX,
290 | y: distY,
291 | matrix: ctm
292 | }
293 | )
294 | : { x: distX, y: distY };
295 |
296 | const dx = dox ? (revX ? -x : x) : 0;
297 | const dy = doy ? (revY ? -y : y) : 0;
298 |
299 | const { x: newX, y: newY } = this._processResizeRestrict(element, { dx, dy });
300 |
301 | return {
302 | x: newX !== null && res.x === null ? distX : res.x,
303 | y: newY !== null && res.y === null ? distY : res.y
304 | };
305 | }, { x: null, y: null })
306 | : { x: null, y: null };
307 |
308 | const isBounding = restrict && (restX !== null || restY !== null);
309 |
310 | const newDx = isBounding ? prevDx : distX;
311 | const newDy = isBounding ? prevDy : distY;
312 |
313 | const nextArgs = {
314 | ...args,
315 | dx: newDx,
316 | dy: newDy,
317 | revX,
318 | revY,
319 | dox,
320 | doy
321 | };
322 |
323 | elements.map((element) => {
324 | const {
325 | transform: {
326 | // scX,
327 | // scY,
328 | ctm
329 | }
330 | } = data.get(element);
331 |
332 | const { x, y } = !isGrouped
333 | ? this._pointToTransform(
334 | {
335 | x: newDx,
336 | y: newDy,
337 | matrix: ctm
338 | }
339 | )
340 | : { x: newDx, y: newDy };
341 |
342 | const dx = dox ? (revX ? -x : x) : 0;
343 | const dy = doy ? (revY ? -y : y) : 0;
344 |
345 | self._resize({
346 | ...nextArgs,
347 | element,
348 | dx,
349 | dy
350 | });
351 | });
352 |
353 | this.storage.cached = {
354 | ...cached,
355 | dist: {
356 | dx: newDx,
357 | dy: newDy
358 | }
359 | };
360 |
361 | this._processControlsResize({ dx: newDx, dy: newDy });
362 |
363 | if (resizeEach) {
364 | observable.notify(
365 | ON_RESIZE,
366 | self,
367 | nextArgs
368 | );
369 | }
370 | }
371 |
372 | if (doDrag && draggable) {
373 | const dx = dox
374 | ? snapToGrid(clientX - relativeX, snap.x)
375 | : 0;
376 |
377 | const dy = doy
378 | ? snapToGrid(clientY - relativeY, snap.y)
379 | : 0;
380 |
381 | const {
382 | cached,
383 | cached: {
384 | dist: {
385 | dx: prevDx = dx,
386 | dy: prevDy = dy
387 | } = {}
388 | } = {}
389 | } = storage;
390 |
391 | const args = {
392 | dx,
393 | dy,
394 | clientX,
395 | clientY,
396 | mouseEvent
397 | };
398 |
399 | const { x: restX, y: restY } = restrict
400 | ? elements.reduce((res, element) => {
401 | const { x, y } = this._processMoveRestrict(element, args);
402 |
403 | return {
404 | x: res.x === null && restrict ? x : res.x,
405 | y: res.y === null && restrict ? y : res.y
406 | };
407 | }, { x: null, y: null })
408 | : { x: null, y: null };
409 |
410 | const newDx = restX !== null && restrict ? prevDx : dx;
411 | const newDy = restY !== null && restrict ? prevDy : dy;
412 |
413 | const nextArgs = {
414 | ...args,
415 | dx: newDx,
416 | dy: newDy
417 | };
418 |
419 | this.storage.cached = {
420 | ...cached,
421 | dist: {
422 | dx: newDx,
423 | dy: newDy
424 | }
425 | };
426 |
427 | elements.map((element) => (
428 | super._drag({
429 | element,
430 | ...nextArgs,
431 | dx: newDx,
432 | dy: newDy
433 | })
434 | ));
435 |
436 | this._processControlsMove({ dx: newDx, dy: newDy });
437 |
438 | if (moveEach) {
439 | observable.notify(
440 | ON_MOVE,
441 | self,
442 | nextArgs
443 | );
444 | }
445 | }
446 |
447 | if (doRotate && rotatable) {
448 | const {
449 | pressang,
450 | center
451 | } = storage;
452 |
453 | const delta = Math.atan2(
454 | clientY - center.y,
455 | clientX - center.x
456 | );
457 | const radians = snapToGrid(delta - pressang, snap.angle);
458 |
459 | if (restrict) {
460 | const isBounding = elements.some((element) => {
461 | const { x: restX, y: restY } = this._processRotateRestrict(element, radians);
462 | return (restX !== null || restY !== null);
463 | });
464 |
465 | if (isBounding) return;
466 | }
467 |
468 | const args = {
469 | clientX,
470 | clientY,
471 | mouseEvent
472 | };
473 |
474 | elements.map((element) => (
475 | self._rotate({
476 | element,
477 | radians,
478 | ...args
479 | })
480 | ));
481 |
482 | this._processControlsRotate({ radians });
483 |
484 | if (rotateEach) {
485 | observable.notify(
486 | ON_ROTATE,
487 | self,
488 | {
489 | radians,
490 | ...args
491 | }
492 | );
493 | }
494 | }
495 |
496 | if (doSetCenter && rotatable) {
497 | const {
498 | bx,
499 | by
500 | } = storage;
501 |
502 | const { x, y } = this._pointToControls(
503 | {
504 | x: clientX,
505 | y: clientY
506 | }
507 | );
508 |
509 | self._moveCenterHandle(
510 | x - bx,
511 | y - by
512 | );
513 | }
514 | }
515 |
516 | _start(e) {
517 | const { clientX, clientY } = e;
518 | const {
519 | elements,
520 | observable,
521 | options: { axis, each },
522 | storage,
523 | storage: { handles }
524 | } = this;
525 |
526 | const isTarget = values(handles).some((hdl) => helper(e.target).is(hdl)) ||
527 | elements.some(element => element.contains(e.target));
528 |
529 | storage.isTarget = isTarget;
530 |
531 | if (!isTarget) return;
532 |
533 | const computed = this._compute(e, elements);
534 |
535 | keys(computed).map(prop => storage[prop] = computed[prop]);
536 |
537 | const {
538 | onRightEdge,
539 | onBottomEdge,
540 | onTopEdge,
541 | onLeftEdge,
542 | handle,
543 | factor,
544 | revX,
545 | revY,
546 | doW,
547 | doH
548 | } = computed;
549 |
550 | const doResize = onRightEdge || onBottomEdge || onTopEdge || onLeftEdge;
551 |
552 | const {
553 | rotator,
554 | center,
555 | radius
556 | } = handles;
557 |
558 | if (isDef(radius)) removeClass(radius, `${LIB_CLASS_PREFIX}hidden`);
559 |
560 | const doRotate = handle.is(rotator),
561 | doSetCenter = isDef(center)
562 | ? handle.is(center)
563 | : false;
564 |
565 | const doDrag = isTarget && !(doRotate || doResize || doSetCenter);
566 |
567 | const nextStorage = {
568 | mouseEvent: e,
569 | clientX,
570 | clientY,
571 | doResize,
572 | doDrag,
573 | doRotate,
574 | doSetCenter,
575 | onExecution: true,
576 | cursor: null,
577 | dox: /\x/.test(axis) && (doResize
578 | ?
579 | handle.is(handles.ml) ||
580 | handle.is(handles.mr) ||
581 | handle.is(handles.tl) ||
582 | handle.is(handles.tr) ||
583 | handle.is(handles.bl) ||
584 | handle.is(handles.br) ||
585 | handle.is(handles.le) ||
586 | handle.is(handles.re)
587 | : true),
588 | doy: /\y/.test(axis) && (doResize
589 | ?
590 | handle.is(handles.br) ||
591 | handle.is(handles.bl) ||
592 | handle.is(handles.bc) ||
593 | handle.is(handles.tr) ||
594 | handle.is(handles.tl) ||
595 | handle.is(handles.tc) ||
596 | handle.is(handles.te) ||
597 | handle.is(handles.be)
598 | : true)
599 | };
600 |
601 | this.storage = {
602 | ...storage,
603 | ...nextStorage
604 | };
605 |
606 | const eventArgs = {
607 | clientX,
608 | clientY
609 | };
610 |
611 | if (doResize) {
612 | super._emitEvent(E_RESIZE_START, eventArgs);
613 | } else if (doRotate) {
614 | super._emitEvent(E_ROTATE_START, eventArgs);
615 | } else if (doDrag) {
616 | super._emitEvent(E_DRAG_START, eventArgs);
617 | }
618 |
619 | const {
620 | move,
621 | resize,
622 | rotate
623 | } = each;
624 |
625 | const actionName = doResize
626 | ? E_RESIZE
627 | : (doRotate ? E_ROTATE : E_DRAG);
628 |
629 | const triggerEvent =
630 | (doResize && resize) ||
631 | (doRotate && rotate) ||
632 | (doDrag && move);
633 |
634 | observable.notify(
635 | ON_GETSTATE,
636 | this,
637 | {
638 | clientX,
639 | clientY,
640 | actionName,
641 | triggerEvent,
642 | factor,
643 | revX,
644 | revY,
645 | doW,
646 | doH
647 | }
648 | );
649 |
650 | this._draw();
651 | }
652 |
653 | _moving(e) {
654 | const { storage = {}, options } = this;
655 |
656 | if (!storage.isTarget) return;
657 |
658 | const { x, y } = this._cursorPoint(e);
659 |
660 | storage.mouseEvent = e;
661 | storage.clientX = x;
662 | storage.clientY = y;
663 | storage.doDraw = true;
664 |
665 | let {
666 | doRotate,
667 | doDrag,
668 | doResize,
669 | cursor
670 | } = storage;
671 |
672 | const {
673 | cursorMove,
674 | cursorResize,
675 | cursorRotate
676 | } = options;
677 |
678 | if (isUndef(cursor)) {
679 | if (doDrag) {
680 | cursor = cursorMove;
681 | } else if (doRotate) {
682 | cursor = cursorRotate;
683 | } else if (doResize) {
684 | cursor = cursorResize;
685 | }
686 | helper(document.body).css({ cursor });
687 | }
688 | }
689 |
690 | _end({ clientX, clientY }) {
691 | const {
692 | elements,
693 | options: { each },
694 | observable,
695 | storage: {
696 | doResize,
697 | doDrag,
698 | doRotate,
699 | doSetCenter,
700 | frame,
701 | handles: { radius },
702 | isTarget
703 | },
704 | proxyMethods
705 | } = this;
706 |
707 | if (!isTarget) return;
708 |
709 | const { actionName = E_DRAG } = [
710 | {
711 | actionName: E_RESIZE,
712 | condition: doResize
713 | },
714 | {
715 | actionName: E_DRAG,
716 | condition: doDrag
717 | },
718 | {
719 | actionName: E_ROTATE,
720 | condition: doRotate
721 | },
722 | {
723 | actionName: E_SET_POINT,
724 | condition: doSetCenter
725 | }
726 | ].find((({ condition }) => condition)) || {};
727 |
728 | elements.map(element => this._applyTransformToElement(element, actionName));
729 |
730 | this._processActions(actionName);
731 | this._updateStorage();
732 |
733 | const eventArgs = {
734 | clientX,
735 | clientY
736 | };
737 |
738 | proxyMethods.onDrop.call(this, eventArgs);
739 |
740 | if (doResize) {
741 | super._emitEvent(E_RESIZE_END, eventArgs);
742 | } else if (doRotate) {
743 | super._emitEvent(E_ROTATE_END, eventArgs);
744 | } else if (doDrag) {
745 | super._emitEvent(E_DRAG_END, eventArgs);
746 | } else if (doSetCenter) {
747 | super._emitEvent(E_SET_POINT_END, eventArgs);
748 | }
749 |
750 | const {
751 | move,
752 | resize,
753 | rotate
754 | } = each;
755 |
756 | const triggerEvent =
757 | (doResize && resize) ||
758 | (doRotate && rotate) ||
759 | (doDrag && move);
760 |
761 | observable.notify(
762 | ON_APPLY,
763 | this,
764 | {
765 | clientX,
766 | clientY,
767 | actionName,
768 | triggerEvent
769 | }
770 | );
771 |
772 | cancelAnimFrame(frame);
773 |
774 | helper(document.body).css({ cursor: 'auto' });
775 | if (isDef(radius)) {
776 | addClass(radius, `${LIB_CLASS_PREFIX}hidden`);
777 | }
778 | }
779 |
780 | _compute(e, elements) {
781 | const {
782 | storage: {
783 | handles,
784 | data
785 | } = {}
786 | } = this;
787 |
788 | const handle = helper(e.target);
789 |
790 | const {
791 | revX,
792 | revY,
793 | doW,
794 | doH,
795 | ...rest
796 | } = this._checkHandles(handle, handles);
797 |
798 | const commonState = this._getCommonState();
799 |
800 | const { x, y } = this._cursorPoint(e);
801 | const { x: bx, y: by } = this._pointToControls({ x, y }, commonState.transform);
802 |
803 | elements.map(element => {
804 | const { transform, ...nextData } = this._getElementState(element, { revX, revY, doW, doH });
805 | const { x: ex, y: ey } = this._pointToTransform({ x, y, matrix: transform.ctm });
806 |
807 | data.set(element, {
808 | ...data.get(element),
809 | ...nextData,
810 | transform,
811 | cx: ex,
812 | cy: ey
813 | });
814 | });
815 |
816 | const pressang = Math.atan2(
817 | y - commonState.center.y,
818 | x - commonState.center.x
819 | );
820 |
821 | return {
822 | data,
823 | ...rest,
824 | handle: values(handles).some(hdl => helper(e.target).is(hdl))
825 | ? handle
826 | : helper(elements[0]),
827 | pressang,
828 | ...commonState,
829 | revX,
830 | revY,
831 | doW,
832 | doH,
833 | relativeX: x,
834 | relativeY: y,
835 | bx,
836 | by
837 | };
838 | }
839 |
840 | _checkHandles(handle, handles) {
841 | const checkIsHandle = hdl => isDef(hdl) ? handle.is(hdl) : false;
842 | const checkAction = items => items.some(key => checkIsHandle(handles[key]));
843 |
844 | const revX = checkAction([TOP_LEFT, MIDDLE_LEFT, BOTTOM_LEFT, TOP_CENTER, LEFT_EDGE]);
845 | const revY = checkAction([TOP_LEFT, TOP_RIGHT, TOP_CENTER, MIDDLE_LEFT, TOP_EDGE]);
846 |
847 | const onTopEdge = checkAction([TOP_CENTER, TOP_RIGHT, TOP_LEFT, TOP_EDGE]);
848 | const onLeftEdge = checkAction([TOP_LEFT, MIDDLE_LEFT, BOTTOM_LEFT, LEFT_EDGE]);
849 | const onRightEdge = checkAction([TOP_RIGHT, MIDDLE_RIGHT, BOTTOM_RIGHT, RIGHT_EDGE]);
850 | const onBottomEdge = checkAction([BOTTOM_RIGHT, BOTTOM_CENTER, BOTTOM_LEFT, BOTTOM_EDGE]);
851 |
852 | const doW = checkAction([MIDDLE_LEFT, MIDDLE_RIGHT, LEFT_EDGE, RIGHT_EDGE]);
853 | const doH = checkAction([TOP_CENTER, BOTTOM_CENTER, BOTTOM_EDGE, TOP_EDGE]);
854 |
855 | return {
856 | revX,
857 | revY,
858 | onTopEdge,
859 | onLeftEdge,
860 | onRightEdge,
861 | onBottomEdge,
862 | doW,
863 | doH
864 | };
865 | }
866 |
867 | _restrictHandler(element, matrix) {
868 | let restrictX = null,
869 | restrictY = null;
870 |
871 | const elBox = this.getBoundingRect(element, matrix);
872 |
873 | const containerBBox = this._getRestrictedBBox();
874 |
875 | const [
876 | [minX, maxX],
877 | [minY, maxY]
878 | ] = getMinMaxOfArray(containerBBox);
879 |
880 | for (let i = 0, len = elBox.length; i < len; i++) {
881 | const [x, y] = elBox[i];
882 |
883 | if (x < minX || x > maxX) {
884 | restrictX = x;
885 | }
886 | if (y < minY || y > maxY) {
887 | restrictY = y;
888 | }
889 | }
890 |
891 | return {
892 | x: restrictX,
893 | y: restrictY
894 | };
895 | }
896 |
897 | _destroy() {
898 | const {
899 | elements,
900 | storage: {
901 | controls,
902 | wrapper
903 | } = {}
904 | } = this;
905 |
906 | [...elements, controls].map(target => (
907 | helper(target)
908 | .off(E_MOUSEDOWN, this._onMouseDown)
909 | .off(E_TOUCHSTART, this._onTouchStart)
910 | ));
911 |
912 | wrapper.parentNode.removeChild(wrapper);
913 | }
914 |
915 | _updateStorage() {
916 | const {
917 | storage,
918 | storage: {
919 | transformOrigin: prevTransformOrigin,
920 | transform: {
921 | controlsMatrix: prevControlsMatrix
922 | } = {},
923 | cached: {
924 | transformOrigin = prevTransformOrigin,
925 | controlsMatrix = prevControlsMatrix
926 | } = {}
927 | }
928 | } = this;
929 |
930 | this.storage = {
931 | ...storage,
932 | doResize: false,
933 | doDrag: false,
934 | doRotate: false,
935 | doSetCenter: false,
936 | doDraw: false,
937 | onExecution: false,
938 | cursor: null,
939 | transformOrigin,
940 | controlsMatrix,
941 | cached: {}
942 | };
943 | }
944 |
945 | notifyMove({ dx, dy }) {
946 | this.elements.map((element) => super._drag({ element, dx, dy }));
947 | this._processControlsMove({ dx, dy });
948 | }
949 |
950 | notifyRotate({ radians, ...rest }) {
951 | const {
952 | elements,
953 | options: {
954 | snap: { angle }
955 | } = {}
956 | } = this;
957 |
958 | elements.map((element) => (
959 | this._rotate({
960 | element,
961 | radians: snapToGrid(radians, angle),
962 | ...rest
963 | })
964 | ));
965 |
966 | this._processControlsRotate({ radians });
967 | }
968 |
969 | notifyResize({ dx, dy, revX, revY, dox, doy }) {
970 | const {
971 | elements,
972 | storage: {
973 | data
974 | },
975 | options: {
976 | isGrouped
977 | }
978 | } = this;
979 |
980 | elements.map((element) => {
981 | const {
982 | transform: {
983 | ctm
984 | }
985 | } = data.get(element);
986 |
987 | const { x, y } = !isGrouped
988 | ? this._pointToTransform(
989 | {
990 | x: dx,
991 | y: dy,
992 | matrix: ctm
993 | }
994 | )
995 | : { x: dx, y: dy };
996 |
997 | this._resize({
998 | element,
999 | dx: dox ? (revX ? -x : x) : 0,
1000 | dy: doy ? (revY ? -y : y) : 0
1001 | });
1002 | });
1003 |
1004 | this._processControlsResize({ dx, dy });
1005 | }
1006 |
1007 | notifyApply({ clientX, clientY, actionName, triggerEvent }) {
1008 | this.proxyMethods.onDrop.call(this, { clientX, clientY });
1009 | if (triggerEvent) {
1010 | this.elements.map((element) => this._applyTransformToElement(element, actionName));
1011 | super._emitEvent(`${actionName}End`, { clientX, clientY });
1012 | }
1013 | }
1014 |
1015 | notifyGetState({ clientX, clientY, actionName, triggerEvent, ...rest }) {
1016 | if (triggerEvent) {
1017 | const {
1018 | elements,
1019 | storage: {
1020 | data
1021 | }
1022 | } = this;
1023 |
1024 | elements.map(element => {
1025 | const nextData = this._getElementState(element, rest);
1026 |
1027 | data.set(element, {
1028 | ...data.get(element),
1029 | ...nextData
1030 | });
1031 | });
1032 |
1033 | const recalc = this._getCommonState();
1034 |
1035 | this.storage = {
1036 | ...this.storage,
1037 | ...recalc
1038 | };
1039 |
1040 | super._emitEvent(`${actionName}Start`, { clientX, clientY });
1041 | }
1042 | }
1043 |
1044 | subscribe({ resize, move, rotate }) {
1045 | const { observable: ob } = this;
1046 |
1047 | if (move || resize || rotate) {
1048 | ob.subscribe(ON_GETSTATE, this)
1049 | .subscribe(ON_APPLY, this);
1050 | }
1051 |
1052 | if (move) {
1053 | ob.subscribe(ON_MOVE, this);
1054 | }
1055 | if (resize) {
1056 | ob.subscribe(ON_RESIZE, this);
1057 | }
1058 | if (rotate) {
1059 | ob.subscribe(ON_ROTATE, this);
1060 | }
1061 | }
1062 |
1063 | unsubscribe() {
1064 | const { observable: ob } = this;
1065 | NOTIFIER_EVENTS.map(eventName => ob.unsubscribe(eventName, this));
1066 | }
1067 |
1068 | disable() {
1069 | const {
1070 | storage,
1071 | proxyMethods,
1072 | elements
1073 | } = this;
1074 |
1075 | if (isUndef(storage)) return;
1076 |
1077 | // unexpected case
1078 | if (storage.onExecution) {
1079 | helper(document)
1080 | .off(E_MOUSEMOVE, this._onMouseMove)
1081 | .off(E_MOUSEUP, this._onMouseUp)
1082 | .off(E_TOUCHMOVE, this._onTouchMove)
1083 | .off(E_TOUCHEND, this._onTouchEnd);
1084 | }
1085 |
1086 | elements.map((element) => removeClass(element, `${LIB_CLASS_PREFIX}drag`));
1087 |
1088 | this.unsubscribe();
1089 | this._destroy();
1090 |
1091 | proxyMethods.onDestroy.call(this, elements);
1092 | delete this.storage;
1093 | }
1094 |
1095 | exeDrag({ dx, dy }) {
1096 | const {
1097 | elements,
1098 | options: {
1099 | draggable
1100 | },
1101 | storage,
1102 | storage: {
1103 | data
1104 | }
1105 | } = this;
1106 | if (!draggable) return;
1107 |
1108 | const commonState = this._getCommonState();
1109 |
1110 | elements.map(element => {
1111 | const nextData = this._getElementState(element, {
1112 | revX: false,
1113 | revY: false,
1114 | doW: false,
1115 | doH: false
1116 | });
1117 |
1118 | data.set(element, {
1119 | ...data.get(element),
1120 | ...nextData
1121 | });
1122 | });
1123 |
1124 | this.storage = {
1125 | ...storage,
1126 | ...commonState
1127 | };
1128 |
1129 | elements.map((element) => {
1130 | super._drag({ element, dx, dy });
1131 | this._applyTransformToElement(element, E_DRAG);
1132 | });
1133 |
1134 | this._processControlsMove({ dx, dy });
1135 | }
1136 |
1137 | exeResize({
1138 | dx,
1139 | dy,
1140 | revX = false,
1141 | revY = false,
1142 | doW = false,
1143 | doH = false
1144 | }) {
1145 | const {
1146 | elements,
1147 | options: {
1148 | resizable
1149 | },
1150 | storage,
1151 | storage: {
1152 | data
1153 | }
1154 | } = this;
1155 | if (!resizable) return;
1156 |
1157 | const commonState = this._getCommonState();
1158 |
1159 | elements.map(element => {
1160 | const nextData = this._getElementState(element, {
1161 | revX,
1162 | revY,
1163 | doW,
1164 | doH
1165 | });
1166 |
1167 | data.set(element, {
1168 | ...data.get(element),
1169 | ...nextData
1170 | });
1171 | });
1172 |
1173 | this.storage = {
1174 | ...storage,
1175 | ...commonState
1176 | };
1177 |
1178 | elements.map((element) => {
1179 | this._resize({ element, dx, dy });
1180 | this._applyTransformToElement(element, E_RESIZE);
1181 | });
1182 |
1183 | this._processControlsMove({ dx, dy });
1184 | }
1185 |
1186 | exeRotate({ delta }) {
1187 | const {
1188 | elements,
1189 | options: {
1190 | rotatable
1191 | },
1192 | storage,
1193 | storage: {
1194 | data
1195 | }
1196 | } = this;
1197 | if (!rotatable) return;
1198 |
1199 | const commonState = this._getCommonState();
1200 |
1201 | elements.map(element => {
1202 | const nextData = this._getElementState(element, {
1203 | revX: false,
1204 | revY: false,
1205 | doW: false,
1206 | doH: false
1207 | });
1208 |
1209 | data.set(element, {
1210 | ...data.get(element),
1211 | ...nextData
1212 | });
1213 | });
1214 |
1215 | this.storage = {
1216 | ...storage,
1217 | ...commonState
1218 | };
1219 |
1220 | elements.map(element => {
1221 | this._rotate({ element, radians: delta });
1222 | this._applyTransformToElement(element, E_ROTATE);
1223 | });
1224 |
1225 | this._processControlsRotate({ radians: delta });
1226 | }
1227 |
1228 | setCenterPoint() {
1229 | throw Error(`'setCenterPoint()' method not implemented`);
1230 | }
1231 |
1232 | resetCenterPoint() {
1233 | warn('"resetCenterPoint" method is replaced by "resetTransformOrigin" and would be removed soon');
1234 | this.setTransformOrigin({ dx: 0, dy: 0 }, false);
1235 | }
1236 |
1237 | setTransformOrigin() {
1238 | throw Error(`'setTransformOrigin()' method not implemented`);
1239 | }
1240 |
1241 | resetTransformOrigin() {
1242 | this.setTransformOrigin({ dx: 0, dy: 0 }, false);
1243 | }
1244 |
1245 | get controls() {
1246 | return this.storage.wrapper;
1247 | }
1248 |
1249 | }
--------------------------------------------------------------------------------