@svgedit/svgcanvas 7.1.4
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.
- package/blur-event.js +156 -0
- package/clear.js +43 -0
- package/coords.js +298 -0
- package/copy-elem.js +45 -0
- package/dataStorage.js +28 -0
- package/dist/svgcanvas.js +515 -0
- package/dist/svgcanvas.js.map +1 -0
- package/draw.js +1064 -0
- package/elem-get-set.js +1077 -0
- package/event.js +1388 -0
- package/history.js +619 -0
- package/historyrecording.js +161 -0
- package/json.js +110 -0
- package/layer.js +228 -0
- package/math.js +221 -0
- package/namespaces.js +40 -0
- package/package.json +54 -0
- package/paint.js +88 -0
- package/paste-elem.js +127 -0
- package/path-actions.js +1237 -0
- package/path-method.js +1012 -0
- package/path.js +781 -0
- package/recalculate.js +794 -0
- package/rollup.config.js +40 -0
- package/sanitize.js +252 -0
- package/select.js +543 -0
- package/selected-elem.js +1297 -0
- package/selection.js +482 -0
- package/svg-exec.js +1289 -0
- package/svgcanvas.js +1347 -0
- package/svgroot.js +36 -0
- package/text-actions.js +530 -0
- package/touch.js +51 -0
- package/undo.js +279 -0
- package/utilities.js +1214 -0
package/selection.js
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for selection.
|
|
3
|
+
* @module selection
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2011 Jeff Schiller
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { NS } from './namespaces.js'
|
|
9
|
+
import {
|
|
10
|
+
getBBox,
|
|
11
|
+
getStrokedBBoxDefaultVisible
|
|
12
|
+
} from './utilities.js'
|
|
13
|
+
import {
|
|
14
|
+
transformPoint,
|
|
15
|
+
transformListToTransform,
|
|
16
|
+
rectsIntersect
|
|
17
|
+
} from './math.js'
|
|
18
|
+
import * as hstry from './history.js'
|
|
19
|
+
import { getClosest } from '../../src/common/util.js'
|
|
20
|
+
|
|
21
|
+
const { BatchCommand } = hstry
|
|
22
|
+
let svgCanvas = null
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @function module:selection.init
|
|
26
|
+
* @param {module:selection.selectionContext} selectionContext
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
29
|
+
export const init = (canvas) => {
|
|
30
|
+
svgCanvas = canvas
|
|
31
|
+
svgCanvas.getMouseTarget = getMouseTargetMethod
|
|
32
|
+
svgCanvas.clearSelection = clearSelectionMethod
|
|
33
|
+
svgCanvas.addToSelection = addToSelectionMethod
|
|
34
|
+
svgCanvas.getIntersectionList = getIntersectionListMethod
|
|
35
|
+
svgCanvas.runExtensions = runExtensionsMethod
|
|
36
|
+
svgCanvas.groupSvgElem = groupSvgElem
|
|
37
|
+
svgCanvas.prepareSvg = prepareSvg
|
|
38
|
+
svgCanvas.recalculateAllSelectedDimensions = recalculateAllSelectedDimensions
|
|
39
|
+
svgCanvas.setRotationAngle = setRotationAngle
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Clears the selection. The 'selected' handler is then optionally called.
|
|
44
|
+
* This should really be an intersection applying to all types rather than a union.
|
|
45
|
+
* @name module:selection.SvgCanvas#clearSelection
|
|
46
|
+
* @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection}
|
|
47
|
+
* @fires module:selection.SvgCanvas#event:selected
|
|
48
|
+
*/
|
|
49
|
+
const clearSelectionMethod = (noCall) => {
|
|
50
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
51
|
+
selectedElements.forEach((elem) => {
|
|
52
|
+
if (!elem) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
svgCanvas.selectorManager.releaseSelector(elem)
|
|
57
|
+
})
|
|
58
|
+
svgCanvas?.setEmptySelectedElements()
|
|
59
|
+
|
|
60
|
+
if (!noCall) {
|
|
61
|
+
svgCanvas.call('selected', svgCanvas.getSelectedElements())
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Adds a list of elements to the selection. The 'selected' handler is then called.
|
|
67
|
+
* @name module:selection.SvgCanvas#addToSelection
|
|
68
|
+
* @type {module:path.EditorContext#addToSelection}
|
|
69
|
+
* @fires module:selection.SvgCanvas#event:selected
|
|
70
|
+
*/
|
|
71
|
+
const addToSelectionMethod = (elemsToAdd, showGrips) => {
|
|
72
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
73
|
+
if (!elemsToAdd.length) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
// find the first null in our selectedElements array
|
|
77
|
+
|
|
78
|
+
let firstNull = 0
|
|
79
|
+
while (firstNull < selectedElements.length) {
|
|
80
|
+
if (selectedElements[firstNull] === null) {
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
++firstNull
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// now add each element consecutively
|
|
87
|
+
let i = elemsToAdd.length
|
|
88
|
+
while (i--) {
|
|
89
|
+
let elem = elemsToAdd[i]
|
|
90
|
+
if (!elem || !elem.getBBox) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (elem.tagName === 'a' && elem.childNodes.length === 1) {
|
|
95
|
+
// Make "a" element's child be the selected element
|
|
96
|
+
elem = elem.firstChild
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// if it's not already there, add it
|
|
100
|
+
if (!selectedElements.includes(elem)) {
|
|
101
|
+
selectedElements[firstNull] = elem
|
|
102
|
+
|
|
103
|
+
// only the first selectedBBoxes element is ever used in the codebase these days
|
|
104
|
+
// if (j === 0) selectedBBoxes[0] = utilsGetBBox(elem);
|
|
105
|
+
firstNull++
|
|
106
|
+
const sel = svgCanvas.selectorManager.requestSelector(elem)
|
|
107
|
+
|
|
108
|
+
if (selectedElements.length > 1) {
|
|
109
|
+
sel.showGrips(false)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!selectedElements.length) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
svgCanvas.call('selected', selectedElements)
|
|
117
|
+
|
|
118
|
+
if (selectedElements.length === 1) {
|
|
119
|
+
svgCanvas.selectorManager
|
|
120
|
+
.requestSelector(selectedElements[0])
|
|
121
|
+
.showGrips(showGrips)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// make sure the elements are in the correct order
|
|
125
|
+
// See: https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
|
|
126
|
+
|
|
127
|
+
selectedElements.sort((a, b) => {
|
|
128
|
+
if (a && b && a.compareDocumentPosition) {
|
|
129
|
+
return 3 - (b.compareDocumentPosition(a) & 6)
|
|
130
|
+
}
|
|
131
|
+
if (!a) {
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
return 0
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Make sure first elements are not null
|
|
138
|
+
while (!selectedElements[0]) {
|
|
139
|
+
selectedElements.shift(0)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* @name module:svgcanvas.SvgCanvas#getMouseTarget
|
|
144
|
+
* @type {module:path.EditorContext#getMouseTarget}
|
|
145
|
+
*/
|
|
146
|
+
const getMouseTargetMethod = (evt) => {
|
|
147
|
+
if (!evt) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
let mouseTarget = evt.target
|
|
151
|
+
|
|
152
|
+
// if it was a <use>, Opera and WebKit return the SVGElementInstance
|
|
153
|
+
if (mouseTarget.correspondingUseElement) {
|
|
154
|
+
mouseTarget = mouseTarget.correspondingUseElement
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// for foreign content, go up until we find the foreignObject
|
|
158
|
+
// WebKit browsers set the mouse target to the svgcanvas div
|
|
159
|
+
if (
|
|
160
|
+
[NS.MATH, NS.HTML].includes(mouseTarget.namespaceURI) &&
|
|
161
|
+
mouseTarget.id !== 'svgcanvas'
|
|
162
|
+
) {
|
|
163
|
+
while (mouseTarget.nodeName !== 'foreignObject') {
|
|
164
|
+
mouseTarget = mouseTarget.parentNode
|
|
165
|
+
if (!mouseTarget) {
|
|
166
|
+
return svgCanvas.getSvgRoot()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Get the desired mouseTarget with jQuery selector-fu
|
|
172
|
+
// If it's root-like, select the root
|
|
173
|
+
const currentLayer = svgCanvas.getCurrentDrawing().getCurrentLayer()
|
|
174
|
+
const svgRoot = svgCanvas.getSvgRoot()
|
|
175
|
+
const container = svgCanvas.getDOMContainer()
|
|
176
|
+
const content = svgCanvas.getSvgContent()
|
|
177
|
+
if ([svgRoot, container, content, currentLayer].includes(mouseTarget)) {
|
|
178
|
+
return svgCanvas.getSvgRoot()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// If it's a selection grip, return the grip parent
|
|
182
|
+
if (getClosest(mouseTarget.parentNode, '#selectorParentGroup')) {
|
|
183
|
+
// While we could instead have just returned mouseTarget,
|
|
184
|
+
// this makes it easier to indentify as being a selector grip
|
|
185
|
+
return svgCanvas.selectorManager.selectorParentGroup
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
while (
|
|
189
|
+
!mouseTarget?.parentNode?.isSameNode(
|
|
190
|
+
svgCanvas.getCurrentGroup() || currentLayer
|
|
191
|
+
)
|
|
192
|
+
) {
|
|
193
|
+
mouseTarget = mouseTarget.parentNode
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return mouseTarget
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* @typedef {module:svgcanvas.ExtensionMouseDownStatus|module:svgcanvas.ExtensionMouseUpStatus|module:svgcanvas.ExtensionIDsUpdatedStatus|module:locale.ExtensionLocaleData[]|void} module:svgcanvas.ExtensionStatus
|
|
200
|
+
* @tutorial ExtensionDocs
|
|
201
|
+
*/
|
|
202
|
+
/**
|
|
203
|
+
* @callback module:svgcanvas.ExtensionVarBuilder
|
|
204
|
+
* @param {string} name The name of the extension
|
|
205
|
+
* @returns {module:svgcanvas.SvgCanvas#event:ext_addLangData}
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* @callback module:svgcanvas.ExtensionNameFilter
|
|
209
|
+
* @param {string} name
|
|
210
|
+
* @returns {boolean}
|
|
211
|
+
*/
|
|
212
|
+
/* eslint-disable max-len */
|
|
213
|
+
/**
|
|
214
|
+
* @todo Consider: Should this return an array by default, so extension results aren't overwritten?
|
|
215
|
+
* @todo Would be easier to document if passing in object with key of action and vars as value; could then define an interface which tied both together
|
|
216
|
+
* @function module:svgcanvas.SvgCanvas#runExtensions
|
|
217
|
+
* @param {"mouseDown"|"mouseMove"|"mouseUp"|"zoomChanged"|"IDsUpdated"|"canvasUpdated"|"toolButtonStateUpdate"|"selectedChanged"|"elementTransition"|"elementChanged"|"langReady"|"langChanged"|"addLangData"|"workareaResized"} action
|
|
218
|
+
* @param {module:svgcanvas.SvgCanvas#event:ext_mouseDown|module:svgcanvas.SvgCanvas#event:ext_mouseMove|module:svgcanvas.SvgCanvas#event:ext_mouseUp|module:svgcanvas.SvgCanvas#event:ext_zoomChanged|module:svgcanvas.SvgCanvas#event:ext_IDsUpdated|module:svgcanvas.SvgCanvas#event:ext_canvasUpdated|module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate|module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementTransition|module:svgcanvas.SvgCanvas#event:ext_elementChanged|module:svgcanvas.SvgCanvas#event:ext_langReady|module:svgcanvas.SvgCanvas#event:ext_langChanged|module:svgcanvas.SvgCanvas#event:ext_addLangData|module:svgcanvas.SvgCanvas#event:ext_workareaResized|module:svgcanvas.ExtensionVarBuilder} [vars]
|
|
219
|
+
* @param {boolean} [returnArray]
|
|
220
|
+
* @returns {GenericArray<module:svgcanvas.ExtensionStatus>|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus.
|
|
221
|
+
*/
|
|
222
|
+
/* eslint-enable max-len */
|
|
223
|
+
const runExtensionsMethod = (
|
|
224
|
+
action,
|
|
225
|
+
vars,
|
|
226
|
+
returnArray
|
|
227
|
+
) => {
|
|
228
|
+
let result = returnArray ? [] : false
|
|
229
|
+
for (const [name, ext] of Object.entries(svgCanvas.getExtensions())) {
|
|
230
|
+
if (typeof vars === 'function') {
|
|
231
|
+
vars = vars(name) // ext, action
|
|
232
|
+
}
|
|
233
|
+
if (ext.eventBased) {
|
|
234
|
+
const event = new CustomEvent('svgedit', {
|
|
235
|
+
detail: {
|
|
236
|
+
action,
|
|
237
|
+
vars
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
document.dispatchEvent(event)
|
|
241
|
+
} else if (ext[action]) {
|
|
242
|
+
if (returnArray) {
|
|
243
|
+
result.push(ext[action](vars))
|
|
244
|
+
} else {
|
|
245
|
+
result = ext[action](vars)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc).
|
|
254
|
+
* Note that 0-opacity, off-screen etc elements are still considered "visible"
|
|
255
|
+
* for this function.
|
|
256
|
+
* @function module:svgcanvas.SvgCanvas#getVisibleElementsAndBBoxes
|
|
257
|
+
* @param {Element} parent - The parent DOM element to search within
|
|
258
|
+
* @returns {ElementAndBBox[]} An array with objects that include:
|
|
259
|
+
*/
|
|
260
|
+
const getVisibleElementsAndBBoxes = (parent) => {
|
|
261
|
+
if (!parent) {
|
|
262
|
+
const svgContent = svgCanvas.getSvgContent()
|
|
263
|
+
parent = svgContent.children // Prevent layers from being included
|
|
264
|
+
}
|
|
265
|
+
const contentElems = []
|
|
266
|
+
const elements = parent.children
|
|
267
|
+
Array.from(elements).forEach((elem) => {
|
|
268
|
+
if (elem.getBBox) {
|
|
269
|
+
contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([elem]) })
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
return contentElems.reverse()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* This method sends back an array or a NodeList full of elements that
|
|
277
|
+
* intersect the multi-select rubber-band-box on the currentLayer only.
|
|
278
|
+
*
|
|
279
|
+
* We brute-force `getIntersectionList` for browsers that do not support it (Firefox).
|
|
280
|
+
*
|
|
281
|
+
* Reference:
|
|
282
|
+
* Firefox does not implement `getIntersectionList()`, see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=501421}.
|
|
283
|
+
* @function module:svgcanvas.SvgCanvas#getIntersectionList
|
|
284
|
+
* @param {SVGRect} rect
|
|
285
|
+
* @returns {Element[]|NodeList} Bbox elements
|
|
286
|
+
*/
|
|
287
|
+
const getIntersectionListMethod = (rect) => {
|
|
288
|
+
const zoom = svgCanvas.getZoom()
|
|
289
|
+
if (!svgCanvas.getRubberBox()) {
|
|
290
|
+
return null
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const parent =
|
|
294
|
+
svgCanvas.getCurrentGroup() ||
|
|
295
|
+
svgCanvas.getCurrentDrawing().getCurrentLayer()
|
|
296
|
+
|
|
297
|
+
let rubberBBox
|
|
298
|
+
if (!rect) {
|
|
299
|
+
rubberBBox = getBBox(svgCanvas.getRubberBox())
|
|
300
|
+
const bb = svgCanvas.getSvgContent().createSVGRect();
|
|
301
|
+
|
|
302
|
+
['x', 'y', 'width', 'height', 'top', 'right', 'bottom', 'left'].forEach(
|
|
303
|
+
(o) => {
|
|
304
|
+
bb[o] = rubberBBox[o] / zoom
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
rubberBBox = bb
|
|
308
|
+
} else {
|
|
309
|
+
rubberBBox = svgCanvas.getSvgContent().createSVGRect()
|
|
310
|
+
rubberBBox.x = rect.x
|
|
311
|
+
rubberBBox.y = rect.y
|
|
312
|
+
rubberBBox.width = rect.width
|
|
313
|
+
rubberBBox.height = rect.height
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const resultList = []
|
|
317
|
+
if (svgCanvas.getCurBBoxes().length === 0) {
|
|
318
|
+
// Cache all bboxes
|
|
319
|
+
svgCanvas.setCurBBoxes(getVisibleElementsAndBBoxes(parent))
|
|
320
|
+
}
|
|
321
|
+
let i = svgCanvas.getCurBBoxes().length
|
|
322
|
+
while (i--) {
|
|
323
|
+
const curBBoxes = svgCanvas.getCurBBoxes()
|
|
324
|
+
if (!rubberBBox.width) {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
if (rectsIntersect(rubberBBox, curBBoxes[i].bbox)) {
|
|
328
|
+
resultList.push(curBBoxes[i].elem)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// addToSelection expects an array, but it's ok to pass a NodeList
|
|
333
|
+
// because using square-bracket notation is allowed:
|
|
334
|
+
// https://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
|
|
335
|
+
return resultList
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @typedef {PlainObject} ElementAndBBox
|
|
340
|
+
* @property {Element} elem - The element
|
|
341
|
+
* @property {module:utilities.BBoxObject} bbox - The element's BBox as retrieved from `getStrokedBBoxDefaultVisible`
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Wrap an SVG element into a group element, mark the group as 'gsvg'.
|
|
346
|
+
* @function module:svgcanvas.SvgCanvas#groupSvgElem
|
|
347
|
+
* @param {Element} elem - SVG element to wrap
|
|
348
|
+
* @returns {void}
|
|
349
|
+
*/
|
|
350
|
+
const groupSvgElem = (elem) => {
|
|
351
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
352
|
+
const g = document.createElementNS(NS.SVG, 'g')
|
|
353
|
+
elem.replaceWith(g)
|
|
354
|
+
g.appendChild(elem)
|
|
355
|
+
dataStorage.put(g, 'gsvg', elem)
|
|
356
|
+
g.id = svgCanvas.getNextId()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Runs the SVG Document through the sanitizer and then updates its paths.
|
|
361
|
+
* @function module:svgcanvas.SvgCanvas#prepareSvg
|
|
362
|
+
* @param {XMLDocument} newDoc - The SVG DOM document
|
|
363
|
+
* @returns {void}
|
|
364
|
+
*/
|
|
365
|
+
const prepareSvg = (newDoc) => {
|
|
366
|
+
svgCanvas.sanitizeSvg(newDoc.documentElement)
|
|
367
|
+
|
|
368
|
+
// convert paths into absolute commands
|
|
369
|
+
const paths = [...newDoc.getElementsByTagNameNS(NS.SVG, 'path')]
|
|
370
|
+
paths.forEach((path) => {
|
|
371
|
+
const convertedPath = svgCanvas.pathActions.convertPath(path)
|
|
372
|
+
path.setAttribute('d', convertedPath)
|
|
373
|
+
svgCanvas.pathActions.fixEnd(path)
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Removes any old rotations if present, prepends a new rotation at the
|
|
379
|
+
* transformed center.
|
|
380
|
+
* @function module:svgcanvas.SvgCanvas#setRotationAngle
|
|
381
|
+
* @param {string|Float} val - The new rotation angle in degrees
|
|
382
|
+
* @param {boolean} preventUndo - Indicates whether the action should be undoable or not
|
|
383
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
384
|
+
* @returns {void}
|
|
385
|
+
*/
|
|
386
|
+
const setRotationAngle = (val, preventUndo) => {
|
|
387
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
388
|
+
// ensure val is the proper type
|
|
389
|
+
val = Number.parseFloat(val)
|
|
390
|
+
const elem = selectedElements[0]
|
|
391
|
+
const oldTransform = elem.getAttribute('transform')
|
|
392
|
+
const bbox = getBBox(elem)
|
|
393
|
+
const cx = bbox.x + bbox.width / 2
|
|
394
|
+
const cy = bbox.y + bbox.height / 2
|
|
395
|
+
const tlist = elem.transform.baseVal
|
|
396
|
+
|
|
397
|
+
// only remove the real rotational transform if present (i.e. at index=0)
|
|
398
|
+
if (tlist.numberOfItems > 0) {
|
|
399
|
+
const xform = tlist.getItem(0)
|
|
400
|
+
if (xform.type === 4) {
|
|
401
|
+
tlist.removeItem(0)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// find Rnc and insert it
|
|
405
|
+
if (val !== 0) {
|
|
406
|
+
const center = transformPoint(
|
|
407
|
+
cx,
|
|
408
|
+
cy,
|
|
409
|
+
transformListToTransform(tlist).matrix
|
|
410
|
+
)
|
|
411
|
+
const Rnc = svgCanvas.getSvgRoot().createSVGTransform()
|
|
412
|
+
Rnc.setRotate(val, center.x, center.y)
|
|
413
|
+
if (tlist.numberOfItems) {
|
|
414
|
+
tlist.insertItemBefore(Rnc, 0)
|
|
415
|
+
} else {
|
|
416
|
+
tlist.appendItem(Rnc)
|
|
417
|
+
}
|
|
418
|
+
} else if (tlist.numberOfItems === 0) {
|
|
419
|
+
elem.removeAttribute('transform')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!preventUndo) {
|
|
423
|
+
// we need to undo it, then redo it so it can be undo-able! :)
|
|
424
|
+
// TODO: figure out how to make changes to transform list undo-able cross-browser?
|
|
425
|
+
let newTransform = elem.getAttribute('transform')
|
|
426
|
+
// new transform is something like: 'rotate(5 1.39625e-8 -11)'
|
|
427
|
+
// we round the x so it becomes 'rotate(5 0 -11)'
|
|
428
|
+
if (newTransform) {
|
|
429
|
+
const newTransformArray = newTransform.split(' ')
|
|
430
|
+
const round = (num) => Math.round(Number(num) + Number.EPSILON)
|
|
431
|
+
const x = round(newTransformArray[1])
|
|
432
|
+
newTransform = `${newTransformArray[0]} ${x} ${newTransformArray[2]}`
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (oldTransform) {
|
|
436
|
+
elem.setAttribute('transform', oldTransform)
|
|
437
|
+
} else {
|
|
438
|
+
elem.removeAttribute('transform')
|
|
439
|
+
}
|
|
440
|
+
svgCanvas.changeSelectedAttribute(
|
|
441
|
+
'transform',
|
|
442
|
+
newTransform,
|
|
443
|
+
selectedElements
|
|
444
|
+
)
|
|
445
|
+
svgCanvas.call('changed', selectedElements)
|
|
446
|
+
}
|
|
447
|
+
// const pointGripContainer = getElement('pathpointgrip_container');
|
|
448
|
+
// if (elem.nodeName === 'path' && pointGripContainer) {
|
|
449
|
+
// pathActions.setPointContainerTransform(elem.getAttribute('transform'));
|
|
450
|
+
// }
|
|
451
|
+
const selector = svgCanvas.selectorManager.requestSelector(
|
|
452
|
+
selectedElements[0]
|
|
453
|
+
)
|
|
454
|
+
selector.resize()
|
|
455
|
+
svgCanvas.getSelector().updateGripCursors(val)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Runs `recalculateDimensions` on the selected elements,
|
|
460
|
+
* adding the changes to a single batch command.
|
|
461
|
+
* @function module:svgcanvas.SvgCanvas#recalculateAllSelectedDimensions
|
|
462
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
463
|
+
* @returns {void}
|
|
464
|
+
*/
|
|
465
|
+
const recalculateAllSelectedDimensions = () => {
|
|
466
|
+
const text =
|
|
467
|
+
svgCanvas.getCurrentResizeMode() === 'none' ? 'position' : 'size'
|
|
468
|
+
const batchCmd = new BatchCommand(text)
|
|
469
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
470
|
+
|
|
471
|
+
selectedElements.forEach((elem) => {
|
|
472
|
+
const cmd = svgCanvas.recalculateDimensions(elem)
|
|
473
|
+
if (cmd) {
|
|
474
|
+
batchCmd.addSubCommand(cmd)
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
if (!batchCmd.isEmpty()) {
|
|
479
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
480
|
+
svgCanvas.call('changed', selectedElements)
|
|
481
|
+
}
|
|
482
|
+
}
|