@svgedit/svgcanvas 7.2.6 → 7.4.1
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/CHANGES.md +6 -0
- package/common/browser.js +104 -37
- package/common/logger.js +151 -0
- package/common/util.js +96 -155
- package/core/blur-event.js +106 -42
- package/core/clear.js +13 -3
- package/core/coords.js +214 -90
- package/core/copy-elem.js +27 -13
- package/core/dataStorage.js +84 -21
- package/core/draw.js +80 -40
- package/core/elem-get-set.js +161 -77
- package/core/event.js +143 -28
- package/core/history.js +51 -31
- package/core/historyrecording.js +4 -2
- package/core/json.js +54 -12
- package/core/layer.js +11 -17
- package/core/math.js +102 -23
- package/core/namespaces.js +5 -5
- package/core/paint.js +100 -23
- package/core/paste-elem.js +58 -19
- package/core/path-actions.js +812 -791
- package/core/path-method.js +236 -37
- package/core/path.js +45 -10
- package/core/recalculate.js +438 -24
- package/core/sanitize.js +71 -34
- package/core/select.js +44 -20
- package/core/selected-elem.js +146 -31
- package/core/selection.js +16 -6
- package/core/svg-exec.js +103 -29
- package/core/svgroot.js +1 -1
- package/core/text-actions.js +327 -306
- package/core/undo.js +20 -5
- package/core/units.js +8 -6
- package/core/utilities.js +316 -203
- package/dist/svgcanvas.js +31616 -53281
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +55 -54
- package/publish.md +1 -6
- package/svgcanvas.d.ts +225 -0
- package/svgcanvas.js +9 -9
- package/vite.config.mjs +20 -0
- package/rollup.config.mjs +0 -38
package/core/sanitize.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { getReverseNS, NS } from './namespaces.js'
|
|
10
|
-
import { getHref, setHref, getUrlFromAttr } from './utilities.js'
|
|
10
|
+
import { getHref, getRefElem, setHref, getUrlFromAttr } from './utilities.js'
|
|
11
|
+
import { warn } from '../common/logger.js'
|
|
11
12
|
|
|
12
13
|
const REVERSE_NS = getReverseNS()
|
|
13
14
|
|
|
@@ -21,7 +22,7 @@ const REVERSE_NS = getReverseNS()
|
|
|
21
22
|
const svgGenericWhiteList = ['class', 'id', 'display', 'transform', 'style']
|
|
22
23
|
const svgWhiteList_ = {
|
|
23
24
|
// SVG Elements
|
|
24
|
-
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
|
|
25
|
+
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'href', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
|
|
25
26
|
circle: ['clip-path', 'clip-rule', 'cx', 'cy', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
26
27
|
clipPath: ['clipPathUnits'],
|
|
27
28
|
defs: [],
|
|
@@ -36,20 +37,24 @@ const svgWhiteList_ = {
|
|
|
36
37
|
feMergeNode: ['in'],
|
|
37
38
|
feMorphology: ['in', 'operator', 'radius'],
|
|
38
39
|
feOffset: ['dx', 'in', 'dy', 'result'],
|
|
39
|
-
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
|
40
|
+
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'href', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
|
40
41
|
foreignObject: ['font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y'],
|
|
41
42
|
g: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
|
|
42
|
-
image: [
|
|
43
|
+
image: [
|
|
44
|
+
'clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity',
|
|
45
|
+
'preserveAspectRatio', 'requiredFeatures', 'systemLanguage', 'viewBox',
|
|
46
|
+
'width', 'x', 'href', 'xlink:href', 'xlink:title', 'y'
|
|
47
|
+
],
|
|
43
48
|
line: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2'],
|
|
44
|
-
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'],
|
|
49
|
+
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'href', 'xlink:href', 'y1', 'y2'],
|
|
45
50
|
marker: ['markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox'],
|
|
46
51
|
mask: ['height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'],
|
|
47
52
|
metadata: [],
|
|
48
53
|
path: ['clip-path', 'clip-rule', 'd', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
49
|
-
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'],
|
|
54
|
+
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'href', 'xlink:href', 'y'],
|
|
50
55
|
polygon: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'sides', 'shape', 'edge', 'point', 'starRadiusMultiplier', 'r', 'radialshift', 'r2', 'orient', 'cx', 'cy'],
|
|
51
56
|
polyline: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'se:connector'],
|
|
52
|
-
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'],
|
|
57
|
+
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'href', 'xlink:href'],
|
|
53
58
|
rect: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'width', 'x', 'y'],
|
|
54
59
|
stop: ['offset', 'requiredFeatures', 'stop-opacity', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform'],
|
|
55
60
|
style: ['type'],
|
|
@@ -57,10 +62,10 @@ const svgWhiteList_ = {
|
|
|
57
62
|
switch: ['requiredFeatures', 'systemLanguage'],
|
|
58
63
|
symbol: ['fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'viewBox', 'width', 'height'],
|
|
59
64
|
text: ['clip-path', 'clip-rule', 'dominant-baseline', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'letter-spacing', 'word-spacing', 'text-decoration', 'textLength', 'lengthAdjust', 'x', 'xml:space', 'y'],
|
|
60
|
-
textPath: ['dominant-baseline', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
|
|
65
|
+
textPath: ['dominant-baseline', 'href', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
|
|
61
66
|
title: [],
|
|
62
67
|
tspan: ['clip-path', 'clip-rule', 'dx', 'dy', 'dominant-baseline', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'textLength', 'x', 'xml:space', 'y'],
|
|
63
|
-
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
|
|
68
|
+
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'href', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
|
|
64
69
|
// Filter Primitives
|
|
65
70
|
feComponentTransfer: ['in', 'result'],
|
|
66
71
|
feFuncR: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'],
|
|
@@ -91,7 +96,7 @@ const svgWhiteList_ = {
|
|
|
91
96
|
mphantom: [],
|
|
92
97
|
mprescripts: [],
|
|
93
98
|
mroot: [],
|
|
94
|
-
mrow: ['xlink:href', 'xlink:type', 'xmlns:xlink'],
|
|
99
|
+
mrow: ['href', 'xlink:href', 'xlink:type', 'xmlns:xlink'],
|
|
95
100
|
mspace: ['depth', 'height', 'width'],
|
|
96
101
|
msqrt: [],
|
|
97
102
|
mstyle: ['displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel'],
|
|
@@ -126,22 +131,24 @@ const svgWhiteList_ = {
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
// add generic attributes to all elements of the whitelist
|
|
129
|
-
|
|
134
|
+
for (const [element, attrs] of Object.entries(svgWhiteList_)) {
|
|
135
|
+
svgWhiteList_[element] = [...attrs, ...svgGenericWhiteList]
|
|
136
|
+
}
|
|
130
137
|
|
|
131
138
|
// Produce a Namespace-aware version of svgWhitelist
|
|
132
139
|
const svgWhiteListNS_ = {}
|
|
133
|
-
|
|
140
|
+
for (const [elt, atts] of Object.entries(svgWhiteList_)) {
|
|
134
141
|
const attNS = {}
|
|
135
|
-
|
|
142
|
+
for (const att of atts) {
|
|
136
143
|
if (att.includes(':')) {
|
|
137
|
-
const
|
|
138
|
-
attNS[
|
|
144
|
+
const [prefix, localName] = att.split(':')
|
|
145
|
+
attNS[localName] = NS[prefix.toUpperCase()]
|
|
139
146
|
} else {
|
|
140
147
|
attNS[att] = att === 'xmlns' ? NS.XMLNS : null
|
|
141
148
|
}
|
|
142
|
-
}
|
|
149
|
+
}
|
|
143
150
|
svgWhiteListNS_[elt] = attNS
|
|
144
|
-
}
|
|
151
|
+
}
|
|
145
152
|
|
|
146
153
|
/**
|
|
147
154
|
* Sanitizes the input node and its children.
|
|
@@ -190,16 +197,20 @@ export const sanitizeSvg = (node) => {
|
|
|
190
197
|
// our whitelist or is a namespace declaration for one of our allowed namespaces
|
|
191
198
|
if (attrNsURI !== allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS &&
|
|
192
199
|
!(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) {
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// We
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
// Special case: allow href attribute even without namespace if it's in the whitelist
|
|
201
|
+
const isHrefAttribute = (attrLocalName === 'href' && allowedAttrs.includes('href'))
|
|
202
|
+
if (!isHrefAttribute) {
|
|
203
|
+
// Bypassing the whitelist to allow se: and oi: prefixes
|
|
204
|
+
// We can add specific namepaces on demand for now.
|
|
205
|
+
// Is there a more appropriate way to do this?
|
|
206
|
+
if (attrName.startsWith('se:') || attrName.startsWith('oi:') || attrName.startsWith('data-')) {
|
|
207
|
+
// We should bypass the namespace aswell
|
|
208
|
+
const seAttrNS = (attrName.startsWith('se:')) ? NS.SE : ((attrName.startsWith('oi:')) ? NS.OI : null)
|
|
209
|
+
seAttrs.push([attrName, attr.value, seAttrNS])
|
|
210
|
+
} else {
|
|
211
|
+
warn(`attribute ${attrName} in element ${node.nodeName} not in whitelist is removed: ${node.outerHTML}`, null, 'sanitize')
|
|
212
|
+
node.removeAttributeNS(attrNsURI, attrLocalName)
|
|
213
|
+
}
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
216
|
|
|
@@ -220,38 +231,64 @@ export const sanitizeSvg = (node) => {
|
|
|
220
231
|
}
|
|
221
232
|
}
|
|
222
233
|
|
|
234
|
+
// If legacy xlink:href is present but href is missing, mirror it to href for modern browsers
|
|
235
|
+
const xlinkHref = node.getAttributeNS(NS.XLINK, 'href')
|
|
236
|
+
if (xlinkHref) {
|
|
237
|
+
node.setAttribute('href', xlinkHref)
|
|
238
|
+
node.removeAttributeNS(NS.XLINK, 'href')
|
|
239
|
+
}
|
|
240
|
+
|
|
223
241
|
Object.values(seAttrs).forEach(([att, val, ns]) => {
|
|
224
242
|
node.setAttributeNS(ns, att, val)
|
|
225
243
|
})
|
|
226
244
|
|
|
227
|
-
// for some elements that have a xlink:href, ensure the URI refers to a local element
|
|
228
|
-
// (but not for links)
|
|
245
|
+
// for some elements that have a xlink:href or href, ensure the URI refers to a local element
|
|
246
|
+
// (but not for links and other elements where external hrefs are allowed)
|
|
229
247
|
const href = getHref(node)
|
|
230
248
|
if (href &&
|
|
231
249
|
['filter', 'linearGradient', 'pattern',
|
|
232
250
|
'radialGradient', 'textPath', 'use'].includes(node.nodeName) && href[0] !== '#') {
|
|
233
251
|
// remove the attribute (but keep the element)
|
|
234
252
|
setHref(node, '')
|
|
235
|
-
|
|
253
|
+
warn(`attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed: ${node.outerHTML}`, null, 'sanitize')
|
|
236
254
|
node.removeAttributeNS(NS.XLINK, 'href')
|
|
255
|
+
node.removeAttribute('href')
|
|
237
256
|
}
|
|
238
257
|
|
|
239
258
|
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
|
|
240
259
|
if (node.nodeName === 'use' && !getHref(node)) {
|
|
241
|
-
|
|
260
|
+
warn(`element ${node.nodeName} without a xlink:href or href is removed: ${node.outerHTML}`, null, 'sanitize')
|
|
242
261
|
node.remove()
|
|
243
262
|
return
|
|
244
263
|
}
|
|
264
|
+
// For <use> elements with missing width/height, derive defaults from referenced viewBox/size for proper sizing/selection
|
|
265
|
+
if (node.nodeName === 'use') {
|
|
266
|
+
const ref = getRefElem(getHref(node))
|
|
267
|
+
if (ref) {
|
|
268
|
+
const refViewBox = ref.getAttribute('viewBox')
|
|
269
|
+
const viewBoxParts = refViewBox ? refViewBox.split(/[\s,]+/).map(Number) : null
|
|
270
|
+
const refWidth = Number(ref.getAttribute('width'))
|
|
271
|
+
const refHeight = Number(ref.getAttribute('height'))
|
|
272
|
+
if (!node.hasAttribute('width')) {
|
|
273
|
+
const width = viewBoxParts?.[2] || refWidth
|
|
274
|
+
if (width) node.setAttribute('width', width)
|
|
275
|
+
}
|
|
276
|
+
if (!node.hasAttribute('height')) {
|
|
277
|
+
const height = viewBoxParts?.[3] || refHeight
|
|
278
|
+
if (height) node.setAttribute('height', height)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
245
282
|
// if the element has attributes pointing to a non-local reference,
|
|
246
283
|
// need to remove the attribute
|
|
247
|
-
|
|
284
|
+
['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'].forEach((attr) => {
|
|
248
285
|
let val = node.getAttribute(attr)
|
|
249
286
|
if (val) {
|
|
250
287
|
val = getUrlFromAttr(val)
|
|
251
288
|
// simply check for first character being a '#'
|
|
252
289
|
if (val && val[0] !== '#') {
|
|
253
290
|
node.setAttribute(attr, '')
|
|
254
|
-
|
|
291
|
+
warn(`attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed: ${node.outerHTML}`, null, 'sanitize')
|
|
255
292
|
node.removeAttribute(attr)
|
|
256
293
|
}
|
|
257
294
|
}
|
|
@@ -264,7 +301,7 @@ export const sanitizeSvg = (node) => {
|
|
|
264
301
|
} else {
|
|
265
302
|
// remove all children from this node and insert them before this node
|
|
266
303
|
// TODO: in the case of animation elements this will hardly ever be correct
|
|
267
|
-
|
|
304
|
+
warn(`element ${node.nodeName} not supported is removed: ${node.outerHTML}`, null, 'sanitize')
|
|
268
305
|
const children = []
|
|
269
306
|
while (node.hasChildNodes()) {
|
|
270
307
|
children.push(parent.insertBefore(node.firstChild, node))
|
package/core/select.js
CHANGED
|
@@ -10,12 +10,37 @@ import { isWebkit } from '../common/browser.js'
|
|
|
10
10
|
import { getRotationAngle, getBBox, getStrokedBBox } from './utilities.js'
|
|
11
11
|
import { transformListToTransform, transformBox, transformPoint, matrixMultiply, getTransformList } from './math.js'
|
|
12
12
|
import { NS } from './namespaces'
|
|
13
|
+
import { warn } from '../common/logger.js'
|
|
13
14
|
|
|
14
15
|
let svgCanvas
|
|
15
|
-
let selectorManager_ // A Singleton
|
|
16
16
|
// change radius if touch screen
|
|
17
17
|
const gripRadius = window.ontouchstart ? 10 : 4
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Private singleton manager for selector state
|
|
21
|
+
*/
|
|
22
|
+
class SelectModule {
|
|
23
|
+
#selectorManager = null
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the select module with canvas
|
|
27
|
+
* @param {Object} canvas - The SVG canvas instance
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
init (canvas) {
|
|
31
|
+
svgCanvas = canvas
|
|
32
|
+
this.#selectorManager = new SelectorManager()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the singleton SelectorManager instance
|
|
37
|
+
* @returns {SelectorManager} The SelectorManager instance
|
|
38
|
+
*/
|
|
39
|
+
getSelectorManager () {
|
|
40
|
+
return this.#selectorManager
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
19
44
|
/**
|
|
20
45
|
* Private class for DOM element selection boxes.
|
|
21
46
|
*/
|
|
@@ -38,14 +63,14 @@ export class Selector {
|
|
|
38
63
|
// this holds a reference to the <g> element that holds all visual elements of the selector
|
|
39
64
|
this.selectorGroup = svgCanvas.createSVGElement({
|
|
40
65
|
element: 'g',
|
|
41
|
-
attr: { id:
|
|
66
|
+
attr: { id: `selectorGroup${this.id}` }
|
|
42
67
|
})
|
|
43
68
|
|
|
44
69
|
// this holds a reference to the path rect
|
|
45
70
|
this.selectorRect = svgCanvas.createSVGElement({
|
|
46
71
|
element: 'path',
|
|
47
72
|
attr: {
|
|
48
|
-
id:
|
|
73
|
+
id: `selectedBox${this.id}`,
|
|
49
74
|
fill: 'none',
|
|
50
75
|
stroke: '#22C',
|
|
51
76
|
'stroke-width': '1',
|
|
@@ -91,11 +116,11 @@ export class Selector {
|
|
|
91
116
|
*/
|
|
92
117
|
showGrips (show) {
|
|
93
118
|
const bShow = show ? 'inline' : 'none'
|
|
94
|
-
|
|
119
|
+
selectModule.getSelectorManager().selectorGripsGroup.setAttribute('display', bShow)
|
|
95
120
|
const elem = this.selectedElement
|
|
96
121
|
this.hasGrips = show
|
|
97
122
|
if (elem && show) {
|
|
98
|
-
this.selectorGroup.append(
|
|
123
|
+
this.selectorGroup.append(selectModule.getSelectorManager().selectorGripsGroup)
|
|
99
124
|
Selector.updateGripCursors(getRotationAngle(elem))
|
|
100
125
|
}
|
|
101
126
|
}
|
|
@@ -108,7 +133,7 @@ export class Selector {
|
|
|
108
133
|
resize (bbox) {
|
|
109
134
|
const dataStorage = svgCanvas.getDataStorage()
|
|
110
135
|
const selectedBox = this.selectorRect
|
|
111
|
-
const mgr =
|
|
136
|
+
const mgr = selectModule.getSelectorManager()
|
|
112
137
|
const selectedGrips = mgr.selectorGrips
|
|
113
138
|
const selected = this.selectedElement
|
|
114
139
|
const zoom = svgCanvas.getZoom()
|
|
@@ -130,7 +155,7 @@ export class Selector {
|
|
|
130
155
|
while (currentElt.parentNode) {
|
|
131
156
|
if (currentElt.parentNode && currentElt.parentNode.tagName === 'g' && currentElt.parentNode.transform) {
|
|
132
157
|
if (currentElt.parentNode.transform.baseVal.numberOfItems) {
|
|
133
|
-
parentTransformationMatrix = matrixMultiply(transformListToTransform(getTransformList(
|
|
158
|
+
parentTransformationMatrix = matrixMultiply(transformListToTransform(getTransformList(currentElt.parentNode)).matrix, parentTransformationMatrix)
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
161
|
currentElt = currentElt.parentNode
|
|
@@ -213,10 +238,7 @@ export class Selector {
|
|
|
213
238
|
nbah = (maxy - miny)
|
|
214
239
|
}
|
|
215
240
|
|
|
216
|
-
const dstr =
|
|
217
|
-
' L' + (nbax + nbaw) + ',' + nbay +
|
|
218
|
-
' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
|
|
219
|
-
' ' + nbax + ',' + (nbay + nbah) + 'z'
|
|
241
|
+
const dstr = `M${nbax},${nbay} L${nbax + nbaw},${nbay} ${nbax + nbaw},${nbay + nbah} ${nbax},${nbay + nbah}z`
|
|
220
242
|
|
|
221
243
|
const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
|
|
222
244
|
|
|
@@ -257,15 +279,15 @@ export class Selector {
|
|
|
257
279
|
* @returns {void}
|
|
258
280
|
*/
|
|
259
281
|
static updateGripCursors (angle) {
|
|
260
|
-
const dirArr = Object.keys(
|
|
282
|
+
const dirArr = Object.keys(selectModule.getSelectorManager().selectorGrips)
|
|
261
283
|
let steps = Math.round(angle / 45)
|
|
262
284
|
if (steps < 0) { steps += 8 }
|
|
263
285
|
while (steps > 0) {
|
|
264
286
|
dirArr.push(dirArr.shift())
|
|
265
287
|
steps--
|
|
266
288
|
}
|
|
267
|
-
Object.values(
|
|
268
|
-
gripElement.setAttribute('style',
|
|
289
|
+
Object.values(selectModule.getSelectorManager().selectorGrips).forEach((gripElement, i) => {
|
|
290
|
+
gripElement.setAttribute('style', `cursor:${dirArr[i]}-resize`)
|
|
269
291
|
})
|
|
270
292
|
}
|
|
271
293
|
}
|
|
@@ -341,10 +363,10 @@ export class SelectorManager {
|
|
|
341
363
|
const grip = svgCanvas.createSVGElement({
|
|
342
364
|
element: 'circle',
|
|
343
365
|
attr: {
|
|
344
|
-
id:
|
|
366
|
+
id: `selectorGrip_resize_${dir}`,
|
|
345
367
|
fill: '#22C',
|
|
346
368
|
r: gripRadius,
|
|
347
|
-
style:
|
|
369
|
+
style: `cursor:${dir}-resize`,
|
|
348
370
|
// This expands the mouse-able area of the grips making them
|
|
349
371
|
// easier to grab with the mouse.
|
|
350
372
|
// This works in Opera and WebKit, but does not work in Firefox
|
|
@@ -462,7 +484,7 @@ export class SelectorManager {
|
|
|
462
484
|
const sel = this.selectorMap[elem.id]
|
|
463
485
|
if (!sel?.locked) {
|
|
464
486
|
// TODO(codedread): Ensure this exists in this module.
|
|
465
|
-
|
|
487
|
+
warn('WARNING! selector was released but was already unlocked', null, 'select')
|
|
466
488
|
}
|
|
467
489
|
for (let i = 0; i < N; ++i) {
|
|
468
490
|
if (this.selectors[i] && this.selectors[i] === sel) {
|
|
@@ -541,6 +563,9 @@ export class SelectorManager {
|
|
|
541
563
|
* @property {module:select.Dimensions} dimensions
|
|
542
564
|
*/
|
|
543
565
|
|
|
566
|
+
// Export singleton instance for backward compatibility
|
|
567
|
+
const selectModule = new SelectModule()
|
|
568
|
+
|
|
544
569
|
/**
|
|
545
570
|
* Initializes this module.
|
|
546
571
|
* @function module:select.init
|
|
@@ -549,12 +574,11 @@ export class SelectorManager {
|
|
|
549
574
|
* @returns {void}
|
|
550
575
|
*/
|
|
551
576
|
export const init = (canvas) => {
|
|
552
|
-
|
|
553
|
-
selectorManager_ = new SelectorManager()
|
|
577
|
+
selectModule.init(canvas)
|
|
554
578
|
}
|
|
555
579
|
|
|
556
580
|
/**
|
|
557
581
|
* @function module:select.getSelectorManager
|
|
558
582
|
* @returns {module:select.SelectorManager} The SelectorManager instance.
|
|
559
583
|
*/
|
|
560
|
-
export const getSelectorManager = () =>
|
|
584
|
+
export const getSelectorManager = () => selectModule.getSelectorManager()
|