@svgedit/svgcanvas 7.1.6 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/common/browser.js +67 -0
  2. package/common/util.js +198 -0
  3. package/{blur-event.js → core/blur-event.js} +0 -0
  4. package/{clear.js → core/clear.js} +0 -0
  5. package/{coords.js → core/coords.js} +0 -0
  6. package/{copy-elem.js → core/copy-elem.js} +0 -0
  7. package/{dataStorage.js → core/dataStorage.js} +0 -0
  8. package/{draw.js → core/draw.js} +1 -1
  9. package/{elem-get-set.js → core/elem-get-set.js} +1 -1
  10. package/{event.js → core/event.js} +19 -5
  11. package/{history.js → core/history.js} +0 -0
  12. package/{historyrecording.js → core/historyrecording.js} +0 -0
  13. package/{json.js → core/json.js} +0 -0
  14. package/{layer.js → core/layer.js} +0 -0
  15. package/{math.js → core/math.js} +0 -0
  16. package/{namespaces.js → core/namespaces.js} +0 -0
  17. package/{paint.js → core/paint.js} +0 -0
  18. package/{paste-elem.js → core/paste-elem.js} +0 -0
  19. package/{path-actions.js → core/path-actions.js} +0 -0
  20. package/{path-method.js → core/path-method.js} +0 -0
  21. package/{path.js → core/path.js} +0 -0
  22. package/{recalculate.js → core/recalculate.js} +17 -5
  23. package/{sanitize.js → core/sanitize.js} +0 -0
  24. package/{select.js → core/select.js} +102 -85
  25. package/{selected-elem.js → core/selected-elem.js} +3 -3
  26. package/{selection.js → core/selection.js} +2 -2
  27. package/{svg-exec.js → core/svg-exec.js} +8 -5
  28. package/{svgroot.js → core/svgroot.js} +0 -0
  29. package/{text-actions.js → core/text-actions.js} +1 -1
  30. package/{touch.js → core/touch.js} +0 -0
  31. package/{undo.js → core/undo.js} +1 -1
  32. package/{units.js → core/units.js} +0 -0
  33. package/{utilities.js → core/utilities.js} +19 -9
  34. package/dist/svgcanvas.js +55562 -397
  35. package/dist/svgcanvas.js.map +1 -1
  36. package/package.json +1 -1
  37. package/{rollup.config.js → rollup.config.mjs} +0 -2
  38. package/svgcanvas.js +27 -27
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Browser detection.
3
+ * @module browser
4
+ * @license MIT
5
+ *
6
+ * @copyright 2010 Jeff Schiller, 2010 Alexis Deveria
7
+ */
8
+
9
+ const NSSVG = 'http://www.w3.org/2000/svg'
10
+
11
+ const { userAgent } = navigator
12
+
13
+ // Note: Browser sniffing should only be used if no other detection method is possible
14
+ const isWebkit_ = userAgent.includes('AppleWebKit')
15
+ const isGecko_ = userAgent.includes('Gecko/')
16
+ const isChrome_ = userAgent.includes('Chrome/')
17
+ const isMac_ = userAgent.includes('Macintosh')
18
+
19
+ // text character positioning (for IE9 and now Chrome)
20
+ const supportsGoodTextCharPos_ = (function () {
21
+ const svgroot = document.createElementNS(NSSVG, 'svg')
22
+ const svgContent = document.createElementNS(NSSVG, 'svg')
23
+ document.documentElement.append(svgroot)
24
+ svgContent.setAttribute('x', 5)
25
+ svgroot.append(svgContent)
26
+ const text = document.createElementNS(NSSVG, 'text')
27
+ text.textContent = 'a'
28
+ svgContent.append(text)
29
+ try { // Chrome now fails here
30
+ const pos = text.getStartPositionOfChar(0).x
31
+ return (pos === 0)
32
+ } catch (err) {
33
+ return false
34
+ } finally {
35
+ svgroot.remove()
36
+ }
37
+ }())
38
+
39
+ // Public API
40
+
41
+ /**
42
+ * @function module:browser.isWebkit
43
+ * @returns {boolean}
44
+ */
45
+ export const isWebkit = () => isWebkit_
46
+ /**
47
+ * @function module:browser.isGecko
48
+ * @returns {boolean}
49
+ */
50
+ export const isGecko = () => isGecko_
51
+ /**
52
+ * @function module:browser.isChrome
53
+ * @returns {boolean}
54
+ */
55
+ export const isChrome = () => isChrome_
56
+
57
+ /**
58
+ * @function module:browser.isMac
59
+ * @returns {boolean}
60
+ */
61
+ export const isMac = () => isMac_
62
+
63
+ /**
64
+ * @function module:browser.supportsGoodTextCharPos
65
+ * @returns {boolean}
66
+ */
67
+ export const supportsGoodTextCharPos = () => supportsGoodTextCharPos_
package/common/util.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @param {any} obj
3
+ * @returns {any}
4
+ */
5
+ export function findPos (obj) {
6
+ let curleft = 0
7
+ let curtop = 0
8
+ if (obj.offsetParent) {
9
+ do {
10
+ curleft += obj.offsetLeft
11
+ curtop += obj.offsetTop
12
+ // eslint-disable-next-line no-cond-assign
13
+ } while (obj = obj.offsetParent)
14
+ return { left: curleft, top: curtop }
15
+ }
16
+ return { left: curleft, top: curtop }
17
+ }
18
+
19
+ export function isObject (item) {
20
+ return (item && typeof item === 'object' && !Array.isArray(item))
21
+ }
22
+
23
+ export function mergeDeep (target, source) {
24
+ const output = Object.assign({}, target)
25
+ if (isObject(target) && isObject(source)) {
26
+ Object.keys(source).forEach((key) => {
27
+ if (isObject(source[key])) {
28
+ if (!(key in target)) { Object.assign(output, { [key]: source[key] }) } else { output[key] = mergeDeep(target[key], source[key]) }
29
+ } else {
30
+ Object.assign(output, { [key]: source[key] })
31
+ }
32
+ })
33
+ }
34
+ return output
35
+ }
36
+
37
+ /**
38
+ * Get the closest matching element up the DOM tree.
39
+ * @param {Element} elem Starting element
40
+ * @param {String} selector Selector to match against (class, ID, data attribute, or tag)
41
+ * @return {Boolean|Element} Returns null if not match found
42
+ */
43
+ export function getClosest (elem, selector) {
44
+ const firstChar = selector.charAt(0)
45
+ const supports = 'classList' in document.documentElement
46
+ let attribute; let value
47
+ // If selector is a data attribute, split attribute from value
48
+ if (firstChar === '[') {
49
+ selector = selector.substr(1, selector.length - 2)
50
+ attribute = selector.split('=')
51
+ if (attribute.length > 1) {
52
+ value = true
53
+ attribute[1] = attribute[1].replace(/"/g, '').replace(/'/g, '')
54
+ }
55
+ }
56
+ // Get closest match
57
+ for (; elem && elem !== document && elem.nodeType === 1; elem = elem.parentNode) {
58
+ // If selector is a class
59
+ if (firstChar === '.') {
60
+ if (supports) {
61
+ if (elem.classList.contains(selector.substr(1))) {
62
+ return elem
63
+ }
64
+ } else {
65
+ if (new RegExp('(^|\\s)' + selector.substr(1) + '(\\s|$)').test(elem.className)) {
66
+ return elem
67
+ }
68
+ }
69
+ }
70
+ // If selector is an ID
71
+ if (firstChar === '#') {
72
+ if (elem.id === selector.substr(1)) {
73
+ return elem
74
+ }
75
+ }
76
+ // If selector is a data attribute
77
+ if (firstChar === '[') {
78
+ if (elem.hasAttribute(attribute[0])) {
79
+ if (value) {
80
+ if (elem.getAttribute(attribute[0]) === attribute[1]) {
81
+ return elem
82
+ }
83
+ } else {
84
+ return elem
85
+ }
86
+ }
87
+ }
88
+ // If selector is a tag
89
+ if (elem.tagName.toLowerCase() === selector) {
90
+ return elem
91
+ }
92
+ }
93
+ return null
94
+ }
95
+
96
+ /**
97
+ * Get all DOM element up the tree that contain a class, ID, or data attribute
98
+ * @param {Node} elem The base element
99
+ * @param {String} selector The class, id, data attribute, or tag to look for
100
+ * @return {Array} Null if no match
101
+ */
102
+ export function getParents (elem, selector) {
103
+ const parents = []
104
+ const firstChar = selector?.charAt(0)
105
+ // Get matches
106
+ for (; elem && elem !== document; elem = elem.parentNode) {
107
+ if (selector) {
108
+ // If selector is a class
109
+ if (firstChar === '.') {
110
+ if (elem.classList.contains(selector.substr(1))) {
111
+ parents.push(elem)
112
+ }
113
+ }
114
+ // If selector is an ID
115
+ if (firstChar === '#') {
116
+ if (elem.id === selector.substr(1)) {
117
+ parents.push(elem)
118
+ }
119
+ }
120
+ // If selector is a data attribute
121
+ if (firstChar === '[') {
122
+ if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
123
+ parents.push(elem)
124
+ }
125
+ }
126
+ // If selector is a tag
127
+ if (elem.tagName.toLowerCase() === selector) {
128
+ parents.push(elem)
129
+ }
130
+ } else {
131
+ parents.push(elem)
132
+ }
133
+ }
134
+ // Return parents if any exist
135
+ return parents.length ? parents : null
136
+ }
137
+
138
+ export function getParentsUntil (elem, parent, selector) {
139
+ const parents = []
140
+ const parentType = parent?.charAt(0)
141
+ const selectorType = selector?.selector.charAt(0)
142
+ // Get matches
143
+ for (; elem && elem !== document; elem = elem.parentNode) {
144
+ // Check if parent has been reached
145
+ if (parent) {
146
+ // If parent is a class
147
+ if (parentType === '.') {
148
+ if (elem.classList.contains(parent.substr(1))) {
149
+ break
150
+ }
151
+ }
152
+ // If parent is an ID
153
+ if (parentType === '#') {
154
+ if (elem.id === parent.substr(1)) {
155
+ break
156
+ }
157
+ }
158
+ // If parent is a data attribute
159
+ if (parentType === '[') {
160
+ if (elem.hasAttribute(parent.substr(1, parent.length - 1))) {
161
+ break
162
+ }
163
+ }
164
+ // If parent is a tag
165
+ if (elem.tagName.toLowerCase() === parent) {
166
+ break
167
+ }
168
+ }
169
+ if (selector) {
170
+ // If selector is a class
171
+ if (selectorType === '.') {
172
+ if (elem.classList.contains(selector.substr(1))) {
173
+ parents.push(elem)
174
+ }
175
+ }
176
+ // If selector is an ID
177
+ if (selectorType === '#') {
178
+ if (elem.id === selector.substr(1)) {
179
+ parents.push(elem)
180
+ }
181
+ }
182
+ // If selector is a data attribute
183
+ if (selectorType === '[') {
184
+ if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
185
+ parents.push(elem)
186
+ }
187
+ }
188
+ // If selector is a tag
189
+ if (elem.tagName.toLowerCase() === selector) {
190
+ parents.push(elem)
191
+ }
192
+ } else {
193
+ parents.push(elem)
194
+ }
195
+ }
196
+ // Return parents if any exist
197
+ return parents.length ? parents : null
198
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  copyElem as utilCopyElem
17
17
  } from './copy-elem.js'
18
- import { getParentsUntil } from '../../src/common/util.js'
18
+ import { getParentsUntil } from '../common/util.js'
19
19
 
20
20
  const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(',')
21
21
 
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  convertToNum
15
15
  } from './units.js'
16
- import { getParents } from '../../src/common/util.js'
16
+ import { getParents } from '../common/util.js'
17
17
 
18
18
  let svgCanvas = null
19
19
 
@@ -17,13 +17,14 @@ import {
17
17
  import * as draw from './draw.js'
18
18
  import * as pathModule from './path.js'
19
19
  import * as hstry from './history.js'
20
- import { findPos } from '../../src/common/util.js'
20
+ import { findPos } from '../../svgcanvas/common/util.js'
21
21
 
22
22
  const {
23
23
  InsertElementCommand
24
24
  } = hstry
25
25
 
26
26
  let svgCanvas = null
27
+ let moveSelectionThresholdReached = false
27
28
 
28
29
  /**
29
30
  * @function module:undo.init
@@ -155,7 +156,13 @@ const mouseMoveEvent = (evt) => {
155
156
  dy = snapToGrid(dy)
156
157
  }
157
158
 
158
- if (dx || dy) {
159
+ // Enable moving selection only if mouse has been moved at least 4 px in any direction
160
+ // This prevents objects from being accidentally moved when (initially) selected
161
+ const deltaThreshold = 4
162
+ const deltaThresholdReached = Math.abs(dx) > deltaThreshold || Math.abs(dy) > deltaThreshold
163
+ moveSelectionThresholdReached = moveSelectionThresholdReached || deltaThresholdReached
164
+
165
+ if (moveSelectionThresholdReached) {
159
166
  selectedElements.forEach((el) => {
160
167
  if (el) {
161
168
  updateTransformList(svgRoot, el, dx, dy)
@@ -277,7 +284,9 @@ const mouseMoveEvent = (evt) => {
277
284
  }
278
285
 
279
286
  translateOrigin.setTranslate(-(left + tx), -(top + ty))
280
- if (evt.shiftKey) {
287
+ // For images, we maintain aspect ratio by default and relax when shift pressed
288
+ const maintainAspectRatio = (selected.tagName !== 'image' && evt.shiftKey) || (selected.tagName === 'image' && !evt.shiftKey)
289
+ if (maintainAspectRatio) {
281
290
  if (sx === 1) {
282
291
  sx = sy
283
292
  } else { sy = sx }
@@ -343,12 +352,16 @@ const mouseMoveEvent = (evt) => {
343
352
  case 'square':
344
353
  case 'rect':
345
354
  case 'image': {
346
- const square = (svgCanvas.getCurrentMode() === 'square') || evt.shiftKey
355
+ // For images, we maintain aspect ratio by default and relax when shift pressed
356
+ const maintainAspectRatio = (svgCanvas.getCurrentMode() === 'square') ||
357
+ (svgCanvas.getCurrentMode() === 'image' && !evt.shiftKey) ||
358
+ (svgCanvas.getCurrentMode() !== 'image' && evt.shiftKey)
359
+
347
360
  let
348
361
  w = Math.abs(x - svgCanvas.getStartX())
349
362
  let h = Math.abs(y - svgCanvas.getStartY())
350
363
  let newX; let newY
351
- if (square) {
364
+ if (maintainAspectRatio) {
352
365
  w = h = Math.max(w, h)
353
366
  newX = svgCanvas.getStartX() < x ? svgCanvas.getStartX() : svgCanvas.getStartX() - w
354
367
  newY = svgCanvas.getStartY() < y ? svgCanvas.getStartY() : svgCanvas.getStartY() - h
@@ -557,6 +570,7 @@ const mouseOutEvent = () => {
557
570
  * @returns {void}
558
571
  */
559
572
  const mouseUpEvent = (evt) => {
573
+ moveSelectionThresholdReached = false
560
574
  if (evt.button === 2) { return }
561
575
  if (!svgCanvas.getStarted()) { return }
562
576
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -15,7 +15,7 @@ import {
15
15
  } from './math.js'
16
16
  import {
17
17
  mergeDeep
18
- } from '../../src/common/util.js'
18
+ } from '../common/util.js'
19
19
 
20
20
  let svgCanvas
21
21
 
@@ -359,7 +359,10 @@ export const recalculateDimensions = (selected) => {
359
359
  childTlist.appendItem(scale)
360
360
  childTlist.appendItem(translateOrigin)
361
361
  } // not rotated
362
- batchCmd.addSubCommand(recalculateDimensions(child))
362
+ const recalculatedDimensions = recalculateDimensions(child)
363
+ if (recalculatedDimensions) {
364
+ batchCmd.addSubCommand(recalculatedDimensions)
365
+ }
363
366
  svgCanvas.setStartTransform(oldStartTransform)
364
367
  } // element
365
368
  } // for each child
@@ -420,7 +423,10 @@ export const recalculateDimensions = (selected) => {
420
423
  } else {
421
424
  childTlist.appendItem(newxlate)
422
425
  }
423
- batchCmd.addSubCommand(recalculateDimensions(child))
426
+ const recalculatedDimensions = recalculateDimensions(child)
427
+ if (recalculatedDimensions) {
428
+ batchCmd.addSubCommand(recalculatedDimensions)
429
+ }
424
430
  // If any <use> have this group as a parent and are
425
431
  // referencing this child, then impose a reverse translate on it
426
432
  // so that when it won't get double-translated
@@ -464,7 +470,10 @@ export const recalculateDimensions = (selected) => {
464
470
  childTlist.clear()
465
471
  childTlist.appendItem(e2m, 0)
466
472
 
467
- batchCmd.addSubCommand(recalculateDimensions(child))
473
+ const recalculatedDimensions = recalculateDimensions(child)
474
+ if (recalculatedDimensions) {
475
+ batchCmd.addSubCommand(recalculatedDimensions)
476
+ }
468
477
  svgCanvas.setStartTransform(oldStartTransform)
469
478
 
470
479
  // Convert stroke
@@ -544,7 +553,10 @@ export const recalculateDimensions = (selected) => {
544
553
  childTlist.appendItem(newxlate)
545
554
  }
546
555
 
547
- batchCmd.addSubCommand(recalculateDimensions(child))
556
+ const recalculatedDimensions = recalculateDimensions(child)
557
+ if (recalculatedDimensions) {
558
+ batchCmd.addSubCommand(recalculatedDimensions)
559
+ }
548
560
  svgCanvas.setStartTransform(oldStartTransform)
549
561
  }
550
562
  }
File without changes
@@ -6,9 +6,10 @@
6
6
  * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
7
7
  */
8
8
 
9
- import { isWebkit } from '../../src/common/browser.js'
9
+ import { isWebkit } from '../common/browser.js'
10
10
  import { getRotationAngle, getBBox, getStrokedBBox } from './utilities.js'
11
- import { transformListToTransform, transformBox, transformPoint } from './math.js'
11
+ import { transformListToTransform, transformBox, transformPoint, matrixMultiply } from './math.js'
12
+ import { NS } from './namespaces'
12
13
 
13
14
  let svgCanvas
14
15
  let selectorManager_ // A Singleton
@@ -122,9 +123,24 @@ export class Selector {
122
123
  offset += 2 / zoom
123
124
  }
124
125
 
126
+ // find the transformations applied to the parent of the selected element
127
+ const svg = document.createElementNS(NS.SVG, 'svg')
128
+ let parentTransformationMatrix = svg.createSVGMatrix()
129
+ let currentElt = selected
130
+ while (currentElt.parentNode) {
131
+ if (currentElt.parentNode && currentElt.parentNode.tagName === 'g' && currentElt.parentNode.transform) {
132
+ if (currentElt.parentNode.transform.baseVal.numberOfItems) {
133
+ parentTransformationMatrix = matrixMultiply(transformListToTransform(selected.parentNode.transform.baseVal).matrix, parentTransformationMatrix)
134
+ }
135
+ }
136
+ currentElt = currentElt.parentNode
137
+ }
138
+
125
139
  // loop and transform our bounding box until we reach our first rotation
126
140
  const tlist = selected.transform.baseVal
127
- const m = transformListToTransform(tlist).matrix
141
+
142
+ // combines the parent transformation with that of the selected element if necessary
143
+ const m = parentTransformationMatrix ? matrixMultiply(parentTransformationMatrix, transformListToTransform(tlist).matrix) : transformListToTransform(tlist).matrix
128
144
 
129
145
  // This should probably be handled somewhere else, but for now
130
146
  // it keeps the selection box correctly positioned when zoomed
@@ -145,92 +161,93 @@ export class Selector {
145
161
  }
146
162
  }
147
163
 
148
- // apply the transforms
149
- const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height
150
- // bbox = {x: l, y: t, width: w, height: h}; // Not in use
151
-
152
- // we need to handle temporary transforms too
153
- // if skewed, get its transformed box, then find its axis-aligned bbox
154
-
155
- // *
156
- offset *= zoom
157
-
158
- const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m)
159
- const { aabox } = nbox
160
- let nbax = aabox.x - offset
161
- let nbay = aabox.y - offset
162
- let nbaw = aabox.width + (offset * 2)
163
- let nbah = aabox.height + (offset * 2)
164
-
165
- // now if the shape is rotated, un-rotate it
166
- const cx = nbax + nbaw / 2
167
- const cy = nbay + nbah / 2
168
-
169
- const angle = getRotationAngle(selected)
170
- if (angle) {
171
- const rot = svgCanvas.getSvgRoot().createSVGTransform()
172
- rot.setRotate(-angle, cx, cy)
173
- const rotm = rot.matrix
174
- nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm)
175
- nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm)
176
- nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm)
177
- nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm)
178
-
179
- // calculate the axis-aligned bbox
180
- const { tl } = nbox
181
- let minx = tl.x
182
- let miny = tl.y
183
- let maxx = tl.x
184
- let maxy = tl.y
185
-
186
- const { min, max } = Math
187
-
188
- minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset
189
- miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset
190
- maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset
191
- maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset
192
-
193
- nbax = minx
194
- nbay = miny
195
- nbaw = (maxx - minx)
196
- nbah = (maxy - miny)
197
- }
164
+ if (bbox) {
165
+ // apply the transforms
166
+ const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height
167
+ // bbox = {x: l, y: t, width: w, height: h}; // Not in use
168
+
169
+ // we need to handle temporary transforms too
170
+ // if skewed, get its transformed box, then find its axis-aligned bbox
171
+
172
+ // *
173
+ offset *= zoom
174
+
175
+ const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m)
176
+ const { aabox } = nbox
177
+ let nbax = aabox.x - offset
178
+ let nbay = aabox.y - offset
179
+ let nbaw = aabox.width + (offset * 2)
180
+ let nbah = aabox.height + (offset * 2)
181
+
182
+ // now if the shape is rotated, un-rotate it
183
+ const cx = nbax + nbaw / 2
184
+ const cy = nbay + nbah / 2
185
+
186
+ const angle = getRotationAngle(selected)
187
+ if (angle) {
188
+ const rot = svgCanvas.getSvgRoot().createSVGTransform()
189
+ rot.setRotate(-angle, cx, cy)
190
+ const rotm = rot.matrix
191
+ nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm)
192
+ nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm)
193
+ nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm)
194
+ nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm)
195
+
196
+ // calculate the axis-aligned bbox
197
+ const { tl } = nbox
198
+ let minx = tl.x
199
+ let miny = tl.y
200
+ let maxx = tl.x
201
+ let maxy = tl.y
202
+
203
+ const { min, max } = Math
204
+
205
+ minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset
206
+ miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset
207
+ maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset
208
+ maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset
209
+
210
+ nbax = minx
211
+ nbay = miny
212
+ nbaw = (maxx - minx)
213
+ nbah = (maxy - miny)
214
+ }
198
215
 
199
- const dstr = 'M' + nbax + ',' + nbay +
200
- ' L' + (nbax + nbaw) + ',' + nbay +
201
- ' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
202
- ' ' + nbax + ',' + (nbay + nbah) + 'z'
216
+ const dstr = 'M' + nbax + ',' + nbay +
217
+ ' L' + (nbax + nbaw) + ',' + nbay +
218
+ ' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
219
+ ' ' + nbax + ',' + (nbay + nbah) + 'z'
220
+
221
+ const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
222
+
223
+ // TODO(codedread): Is this needed?
224
+ // if (selected === selectedElements[0]) {
225
+ this.gripCoords = {
226
+ nw: [nbax, nbay],
227
+ ne: [nbax + nbaw, nbay],
228
+ sw: [nbax, nbay + nbah],
229
+ se: [nbax + nbaw, nbay + nbah],
230
+ n: [nbax + (nbaw) / 2, nbay],
231
+ w: [nbax, nbay + (nbah) / 2],
232
+ e: [nbax + nbaw, nbay + (nbah) / 2],
233
+ s: [nbax + (nbaw) / 2, nbay + nbah]
234
+ }
235
+ selectedBox.setAttribute('d', dstr)
236
+ this.selectorGroup.setAttribute('transform', xform)
237
+ Object.entries(this.gripCoords).forEach(([dir, coords]) => {
238
+ selectedGrips[dir].setAttribute('cx', coords[0])
239
+ selectedGrips[dir].setAttribute('cy', coords[1])
240
+ })
203
241
 
204
- const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
242
+ // we want to go 20 pixels in the negative transformed y direction, ignoring scale
243
+ mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
244
+ mgr.rotateGripConnector.setAttribute('y1', nbay)
245
+ mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
246
+ mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
205
247
 
206
- // TODO(codedread): Is this needed?
207
- // if (selected === selectedElements[0]) {
208
- this.gripCoords = {
209
- nw: [nbax, nbay],
210
- ne: [nbax + nbaw, nbay],
211
- sw: [nbax, nbay + nbah],
212
- se: [nbax + nbaw, nbay + nbah],
213
- n: [nbax + (nbaw) / 2, nbay],
214
- w: [nbax, nbay + (nbah) / 2],
215
- e: [nbax + nbaw, nbay + (nbah) / 2],
216
- s: [nbax + (nbaw) / 2, nbay + nbah]
248
+ mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
249
+ mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
217
250
  }
218
- selectedBox.setAttribute('d', dstr)
219
- this.selectorGroup.setAttribute('transform', xform)
220
- Object.entries(this.gripCoords).forEach(([dir, coords]) => {
221
- selectedGrips[dir].setAttribute('cx', coords[0])
222
- selectedGrips[dir].setAttribute('cy', coords[1])
223
- })
224
-
225
- // we want to go 20 pixels in the negative transformed y direction, ignoring scale
226
- mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
227
- mgr.rotateGripConnector.setAttribute('y1', nbay)
228
- mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
229
- mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
230
-
231
- mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
232
- mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
233
- // }
234
251
  }
235
252
 
236
253
  // STATIC methods