@joint/core 4.1.2 → 4.2.0-alpha.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 (45) hide show
  1. package/README.md +1 -1
  2. package/dist/geometry.js +128 -123
  3. package/dist/geometry.min.js +2 -2
  4. package/dist/joint.d.ts +79 -16
  5. package/dist/joint.js +2249 -1730
  6. package/dist/joint.min.js +2 -2
  7. package/dist/joint.nowrap.js +2248 -1727
  8. package/dist/joint.nowrap.min.js +2 -2
  9. package/dist/vectorizer.js +469 -272
  10. package/dist/vectorizer.min.js +2 -2
  11. package/dist/version.mjs +1 -1
  12. package/package.json +28 -22
  13. package/src/V/create.mjs +51 -0
  14. package/src/V/index.mjs +69 -154
  15. package/src/V/namespace.mjs +9 -0
  16. package/src/V/transform.mjs +183 -0
  17. package/src/V/traverse.mjs +16 -0
  18. package/src/anchors/index.mjs +140 -33
  19. package/src/cellTools/Boundary.mjs +1 -1
  20. package/src/cellTools/Control.mjs +1 -1
  21. package/src/connectionPoints/index.mjs +24 -9
  22. package/src/connectionStrategies/index.mjs +1 -1
  23. package/src/connectors/jumpover.mjs +1 -1
  24. package/src/dia/Cell.mjs +6 -2
  25. package/src/dia/CellView.mjs +47 -39
  26. package/src/dia/Element.mjs +79 -35
  27. package/src/dia/ElementView.mjs +9 -3
  28. package/src/dia/HighlighterView.mjs +32 -11
  29. package/src/dia/Paper.mjs +134 -22
  30. package/src/dia/PaperLayer.mjs +9 -2
  31. package/src/dia/attributes/text.mjs +12 -3
  32. package/src/dia/layers/GridLayer.mjs +5 -0
  33. package/src/dia/ports.mjs +152 -39
  34. package/src/env/index.mjs +1 -1
  35. package/src/g/rect.mjs +7 -0
  36. package/src/highlighters/stroke.mjs +1 -1
  37. package/src/linkAnchors/index.mjs +2 -2
  38. package/src/linkTools/Anchor.mjs +2 -2
  39. package/src/linkTools/Vertices.mjs +4 -6
  40. package/src/mvc/Dom/methods.mjs +2 -2
  41. package/src/util/util.mjs +1 -1
  42. package/src/util/utilHelpers.mjs +2 -0
  43. package/types/geometry.d.ts +2 -0
  44. package/types/joint.d.ts +81 -20
  45. package/src/V/annotation.mjs +0 -0
@@ -0,0 +1,51 @@
1
+ import * as ns from './namespace.mjs';
2
+
3
+ /**
4
+ * @constant {boolean}
5
+ * @description Indicates the environment supports SVG.
6
+ */
7
+ export const isSVGSupported = typeof window === 'object' && !!window.SVGAngle;
8
+
9
+ /**
10
+ * @constant {string}
11
+ * @description The version of the SVG document.
12
+ */
13
+ export const SVG_VERSION = '1.1';
14
+
15
+ /**
16
+ * @constant {SVGSVGElement}
17
+ * @description The detached SVG document for various internal purposes.
18
+ * e.g. SVGMatrix has no constructor, so the only way to create it is
19
+ * to create an SVG document and then call `createSVGMatrix()`.
20
+ */
21
+ export const internalSVGDocument = isSVGSupported
22
+ ? createSVGDocument()
23
+ : null;
24
+
25
+ /**
26
+ * @constant {SVGGElement}
27
+ * @description The detached SVG group element for various internal purposes.
28
+ */
29
+ export const internalSVGGroup = isSVGSupported
30
+ ? createSVGElement('g')
31
+ : null;
32
+
33
+ /**
34
+ * @returns {SVGSVGElement}
35
+ * @description Creates an SVG document.
36
+ */
37
+ export function createSVGDocument() {
38
+ const svg = createSVGElement('svg');
39
+ svg.setAttributeNS(ns.xmlns, 'xmlns:xlink', ns.xlink);
40
+ svg.setAttribute('version', SVG_VERSION);
41
+ return svg;
42
+ }
43
+
44
+ /**
45
+ * @param {string} name
46
+ * @returns {SVGElement}
47
+ * @description Creates an SVG element with the given name.
48
+ */
49
+ export function createSVGElement(name) {
50
+ return document.createElementNS(ns.svg, name);
51
+ }
package/src/V/index.mjs CHANGED
@@ -5,13 +5,20 @@
5
5
  // The only Vectorizer dependency is the Geometry library.
6
6
 
7
7
  import * as g from '../g/index.mjs';
8
+ import * as ns from './namespace.mjs';
9
+ import { isSVGSupported, internalSVGDocument, SVG_VERSION, createSVGDocument, createSVGElement } from './create.mjs';
10
+ import {
11
+ createIdentityMatrix, createMatrix, getNodeMatrix, isSVGMatrix,
12
+ getRelativeTransformation, getRelativeTransformationSafe,
13
+ matrixToTransformString, createMatrixFromTransformString,
14
+ transformNode, replaceTransformNode,
15
+ } from './transform.mjs';
16
+ import { getCommonAncestor } from './traverse.mjs';
8
17
 
9
18
  const V = (function() {
10
19
 
11
- var hasSvg = typeof window === 'object' && !!window.SVGAngle;
12
-
13
20
  // SVG support is required.
14
- if (!hasSvg) {
21
+ if (!isSVGSupported) {
15
22
 
16
23
  // Return a function that throws an error when it is used.
17
24
  return function() {
@@ -19,17 +26,6 @@ const V = (function() {
19
26
  };
20
27
  }
21
28
 
22
- // XML namespaces.
23
- var ns = {
24
- svg: 'http://www.w3.org/2000/svg',
25
- xmlns: 'http://www.w3.org/2000/xmlns/',
26
- xml: 'http://www.w3.org/XML/1998/namespace',
27
- xlink: 'http://www.w3.org/1999/xlink',
28
- xhtml: 'http://www.w3.org/1999/xhtml'
29
- };
30
-
31
- var SVGVersion = '1.1';
32
-
33
29
  // Declare shorthands to the most used math functions.
34
30
  var math = Math;
35
31
  var PI = math.PI;
@@ -92,7 +88,7 @@ const V = (function() {
92
88
 
93
89
  } else {
94
90
 
95
- el = document.createElementNS(ns.svg, el);
91
+ el = createSVGElement(el);
96
92
  }
97
93
 
98
94
  V.ensureId(el);
@@ -125,37 +121,42 @@ const V = (function() {
125
121
  * @param {SVGGElement} toElem
126
122
  * @returns {SVGMatrix}
127
123
  */
128
- VPrototype.getTransformToElement = function(target) {
129
- var node = this.node;
130
- if (V.isSVGGraphicsElement(target) && V.isSVGGraphicsElement(node)) {
131
- var targetCTM = V.toNode(target).getScreenCTM();
132
- var nodeCTM = node.getScreenCTM();
133
- if (targetCTM && nodeCTM) {
134
- return targetCTM.inverse().multiply(nodeCTM);
124
+ VPrototype.getTransformToElement = function(target, opt) {
125
+ const node = this.node;
126
+ const targetNode = V.toNode(target);
127
+ let m;
128
+ if (V.isSVGGraphicsElement(targetNode) && V.isSVGGraphicsElement(node)) {
129
+ if (opt && opt.safe) {
130
+ // Use the traversal method to get the transformation matrix.
131
+ m = getRelativeTransformationSafe(node, targetNode);
132
+ } else {
133
+ m = getRelativeTransformation(node, targetNode);
135
134
  }
136
135
  }
137
- // Could not get actual transformation matrix
138
- return V.createSVGMatrix();
136
+ return m || createIdentityMatrix();
139
137
  };
140
138
 
139
+
141
140
  /**
142
141
  * @param {SVGMatrix} matrix
143
142
  * @param {Object=} opt
144
143
  * @returns {Vectorizer|SVGMatrix} Setter / Getter
145
144
  */
146
145
  VPrototype.transform = function(matrix, opt) {
146
+ const node = this.node;
147
147
 
148
- var node = this.node;
148
+ // Getter
149
149
  if (V.isUndefined(matrix)) {
150
- return V.transformStringToMatrix(this.attr('transform'));
150
+ return getNodeMatrix(node) || createIdentityMatrix();
151
151
  }
152
152
 
153
+ // Setter
153
154
  if (opt && opt.absolute) {
154
- return this.attr('transform', V.matrixToTransformString(matrix));
155
+ replaceTransformNode(node, matrix);
156
+ } else {
157
+ transformNode(node, matrix);
155
158
  }
156
159
 
157
- var svgTransform = V.createSVGTransform(matrix);
158
- node.transform.baseVal.appendItem(svgTransform);
159
160
  return this;
160
161
  };
161
162
 
@@ -250,7 +251,7 @@ const V = (function() {
250
251
 
251
252
  box = node.getBBox();
252
253
 
253
- } catch (e) {
254
+ } catch {
254
255
 
255
256
  // Fallback for IE.
256
257
  box = {
@@ -303,7 +304,7 @@ const V = (function() {
303
304
  if (!options.recursive) {
304
305
  try {
305
306
  outputBBox = node.getBBox();
306
- } catch (e) {
307
+ } catch {
307
308
  // Fallback for IE.
308
309
  outputBBox = {
309
310
  x: node.clientLeft,
@@ -924,9 +925,9 @@ const V = (function() {
924
925
  var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse());
925
926
  var globalToLocalMatrix = this.getTransformToElement(svg).inverse();
926
927
 
927
- } catch (e) {
928
+ } catch {
928
929
  // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`)
929
- // We have to make do with the original coordianates.
930
+ // We have to make do with the original coordinates.
930
931
  return p;
931
932
  }
932
933
 
@@ -986,7 +987,7 @@ const V = (function() {
986
987
  translateToOrigin.matrix.multiply(
987
988
  ctm.scale(scale.sx, scale.sy)))));
988
989
 
989
- this.attr('transform', V.matrixToTransformString(transform.matrix));
990
+ this.attr('transform', matrixToTransformString(transform.matrix));
990
991
 
991
992
  return this;
992
993
  };
@@ -1004,7 +1005,7 @@ const V = (function() {
1004
1005
  this.append(animateMotion);
1005
1006
  try {
1006
1007
  animateMotion.node.beginElement();
1007
- } catch (e) {
1008
+ } catch {
1008
1009
  // Fallback for IE 9.
1009
1010
  // Run the animation programmatically if FakeSmile (`http://leunen.me/fakesmile/`) present
1010
1011
  if (document.documentElement.getAttribute('smiling') === 'fake') {
@@ -1287,15 +1288,12 @@ const V = (function() {
1287
1288
  V.createSvgDocument = function(content) {
1288
1289
 
1289
1290
  if (content) {
1290
- const XMLString = `<svg xmlns="${ns.svg}" xmlns:xlink="${ns.xlink}" version="${SVGVersion}">${content}</svg>`;
1291
+ const XMLString = `<svg xmlns="${ns.svg}" xmlns:xlink="${ns.xlink}" version="${SVG_VERSION}">${content}</svg>`;
1291
1292
  const { documentElement } = V.parseXML(XMLString, { async: false });
1292
1293
  return documentElement;
1293
1294
  }
1294
1295
 
1295
- const svg = document.createElementNS(ns.svg, 'svg');
1296
- svg.setAttributeNS(ns.xmlns, 'xmlns:xlink', ns.xlink);
1297
- svg.setAttribute('version', SVGVersion);
1298
- return svg;
1296
+ return createSVGDocument();
1299
1297
  };
1300
1298
 
1301
1299
  V.createSVGStyle = function(stylesheet) {
@@ -1370,7 +1368,7 @@ const V = (function() {
1370
1368
  }
1371
1369
 
1372
1370
  xml = parser.parseFromString(data, 'text/xml');
1373
- } catch (error) {
1371
+ } catch {
1374
1372
  xml = undefined;
1375
1373
  }
1376
1374
 
@@ -1389,6 +1387,7 @@ const V = (function() {
1389
1387
  // List of attributes for which not to split camel case words.
1390
1388
  // It contains known SVG attribute names and may be extended with user-defined attribute names.
1391
1389
  [
1390
+ 'attributeName',
1392
1391
  'baseFrequency',
1393
1392
  'baseProfile',
1394
1393
  'clipPathUnits',
@@ -1426,6 +1425,7 @@ const V = (function() {
1426
1425
  'refY',
1427
1426
  'requiredExtensions',
1428
1427
  'requiredFeatures',
1428
+ 'repeatCount',
1429
1429
  'specularConstant',
1430
1430
  'specularExponent',
1431
1431
  'spreadMethod',
@@ -1442,7 +1442,7 @@ const V = (function() {
1442
1442
  'viewTarget', // deprecated
1443
1443
  'xChannelSelector',
1444
1444
  'yChannelSelector',
1445
- 'zoomAndPan' // deprecated
1445
+ 'zoomAndPan', // deprecated
1446
1446
  ].forEach((name) => _attributeNames[name] = name);
1447
1447
 
1448
1448
  _attributeNames['xlinkShow'] = 'xlink:show';
@@ -1511,108 +1511,22 @@ const V = (function() {
1511
1511
  // ReDoS mitigation: Use an anchor at the beginning of the match
1512
1512
  // ReDoS mitigation: Avoid backtracking (uses `[^()]+` instead of `.*?`)
1513
1513
  // ReDoS mitigation: Don't match initial `(` inside repeated part
1514
- // The following regex needs to use /g (= cannot use capturing groups)
1515
- V.transformRegex = /\b\w+\([^()]+\)/g;
1516
1514
  // The following regexes need to use capturing groups (= cannot use /g)
1517
1515
  V.transformFunctionRegex = /\b(\w+)\(([^()]+)\)/;
1518
1516
  V.transformTranslateRegex = /\btranslate\(([^()]+)\)/;
1519
1517
  V.transformRotateRegex = /\brotate\(([^()]+)\)/;
1520
1518
  V.transformScaleRegex = /\bscale\(([^()]+)\)/;
1521
1519
 
1522
- V.transformStringToMatrix = function(transform) {
1523
-
1524
- // Initialize result matrix as identity matrix
1525
- let transformationMatrix = V.createSVGMatrix();
1526
-
1527
- // Note: Multiple transform functions are allowed in `transform` string
1528
- // `match()` returns `null` if none found
1529
- const transformMatches = transform && transform.match(V.transformRegex);
1530
- if (!transformMatches) {
1531
- // Return identity matrix
1532
- return transformationMatrix;
1533
- }
1534
-
1535
- const numMatches = transformMatches.length;
1536
- for (let i = 0; i < numMatches; i++) {
1537
-
1538
- const transformMatch = transformMatches[i];
1539
- // Use same regex as above, but with capturing groups
1540
- // `match()` returns values of capturing groups as `[1]`, `[2]`
1541
- const transformFunctionMatch = transformMatch.match(V.transformFunctionRegex);
1542
- if (transformFunctionMatch) {
1543
-
1544
- let sx, sy, tx, ty, angle;
1545
- let ctm = V.createSVGMatrix();
1546
- const transformFunction = transformFunctionMatch[1].toLowerCase();
1547
- const args = transformFunctionMatch[2].split(V.transformSeparatorRegex);
1548
- switch (transformFunction) {
1549
-
1550
- case 'scale':
1551
- sx = parseFloat(args[0]);
1552
- sy = (args[1] === undefined) ? sx : parseFloat(args[1]);
1553
- ctm = ctm.scaleNonUniform(sx, sy);
1554
- break;
1555
-
1556
- case 'translate':
1557
- tx = parseFloat(args[0]);
1558
- ty = parseFloat(args[1]);
1559
- ctm = ctm.translate(tx, ty);
1560
- break;
1561
-
1562
- case 'rotate':
1563
- angle = parseFloat(args[0]);
1564
- tx = parseFloat(args[1]) || 0;
1565
- ty = parseFloat(args[2]) || 0;
1566
- if (tx !== 0 || ty !== 0) {
1567
- ctm = ctm.translate(tx, ty).rotate(angle).translate(-tx, -ty);
1568
- } else {
1569
- ctm = ctm.rotate(angle);
1570
- }
1571
- break;
1572
-
1573
- case 'skewx':
1574
- angle = parseFloat(args[0]);
1575
- ctm = ctm.skewX(angle);
1576
- break;
1577
-
1578
- case 'skewy':
1579
- angle = parseFloat(args[0]);
1580
- ctm = ctm.skewY(angle);
1581
- break;
1582
-
1583
- case 'matrix':
1584
- ctm.a = parseFloat(args[0]);
1585
- ctm.b = parseFloat(args[1]);
1586
- ctm.c = parseFloat(args[2]);
1587
- ctm.d = parseFloat(args[3]);
1588
- ctm.e = parseFloat(args[4]);
1589
- ctm.f = parseFloat(args[5]);
1590
- break;
1591
-
1592
- default:
1593
- continue;
1594
- }
1595
-
1596
- // Multiply current transformation into result matrix
1597
- transformationMatrix = transformationMatrix.multiply(ctm);
1598
- }
1599
1520
 
1521
+ V.transformStringToMatrix = function(transform) {
1522
+ let matrix;
1523
+ if (V.isString(transform)) {
1524
+ matrix = createMatrixFromTransformString(transform);
1600
1525
  }
1601
- return transformationMatrix;
1526
+ return matrix || createIdentityMatrix();
1602
1527
  };
1603
1528
 
1604
- V.matrixToTransformString = function(matrix) {
1605
- matrix || (matrix = true);
1606
-
1607
- return 'matrix(' +
1608
- (matrix.a !== undefined ? matrix.a : 1) + ',' +
1609
- (matrix.b !== undefined ? matrix.b : 0) + ',' +
1610
- (matrix.c !== undefined ? matrix.c : 0) + ',' +
1611
- (matrix.d !== undefined ? matrix.d : 1) + ',' +
1612
- (matrix.e !== undefined ? matrix.e : 0) + ',' +
1613
- (matrix.f !== undefined ? matrix.f : 0) +
1614
- ')';
1615
- };
1529
+ V.matrixToTransformString = matrixToTransformString;
1616
1530
 
1617
1531
  V.parseTransformString = function(transform) {
1618
1532
 
@@ -1780,35 +1694,25 @@ const V = (function() {
1780
1694
  return node instanceof SVGElement && typeof node.getScreenCTM === 'function';
1781
1695
  };
1782
1696
 
1783
- var svgDocument = V('svg').node;
1784
-
1785
- V.createSVGMatrix = function(matrix) {
1786
-
1787
- var svgMatrix = svgDocument.createSVGMatrix();
1788
- for (var component in matrix) {
1789
- svgMatrix[component] = matrix[component];
1790
- }
1791
-
1792
- return svgMatrix;
1793
- };
1697
+ V.createSVGMatrix = createMatrix;
1794
1698
 
1795
1699
  V.createSVGTransform = function(matrix) {
1796
1700
 
1797
1701
  if (!V.isUndefined(matrix)) {
1798
1702
 
1799
- if (!(matrix instanceof SVGMatrix)) {
1800
- matrix = V.createSVGMatrix(matrix);
1703
+ if (!isSVGMatrix(matrix)) {
1704
+ matrix = createMatrix(matrix);
1801
1705
  }
1802
1706
 
1803
- return svgDocument.createSVGTransformFromMatrix(matrix);
1707
+ return internalSVGDocument.createSVGTransformFromMatrix(matrix);
1804
1708
  }
1805
1709
 
1806
- return svgDocument.createSVGTransform();
1710
+ return internalSVGDocument.createSVGTransform();
1807
1711
  };
1808
1712
 
1809
1713
  V.createSVGPoint = function(x, y) {
1810
1714
 
1811
- var p = svgDocument.createSVGPoint();
1715
+ var p = internalSVGDocument.createSVGPoint();
1812
1716
  p.x = x;
1813
1717
  p.y = y;
1814
1718
  return p;
@@ -1816,7 +1720,7 @@ const V = (function() {
1816
1720
 
1817
1721
  V.transformRect = function(r, matrix) {
1818
1722
 
1819
- var p = svgDocument.createSVGPoint();
1723
+ var p = internalSVGDocument.createSVGPoint();
1820
1724
 
1821
1725
  p.x = r.x;
1822
1726
  p.y = r.y;
@@ -2083,8 +1987,8 @@ const V = (function() {
2083
1987
 
2084
1988
  line = V(line);
2085
1989
  var d = [
2086
- 'M', line.attr('x1'), line.attr('y1'),
2087
- 'L', line.attr('x2'), line.attr('y2')
1990
+ 'M', line.attr('x1') || '0', line.attr('y1') || '0',
1991
+ 'L', line.attr('x2') || '0', line.attr('y2') || '0'
2088
1992
  ].join(' ');
2089
1993
  return d;
2090
1994
  };
@@ -2635,7 +2539,18 @@ const V = (function() {
2635
2539
  };
2636
2540
  })();
2637
2541
 
2638
- V.namespace = ns;
2542
+ /**
2543
+ *
2544
+ * @param {SVGElement|V} node1
2545
+ * @param {SVGElement|V} node2
2546
+ * @returns {SVGElement|null}
2547
+ */
2548
+ V.getCommonAncestor = function(node1, node2) {
2549
+ if (!node1 || !node2) return null;
2550
+ return getCommonAncestor(V.toNode(node1), V.toNode(node2));
2551
+ };
2552
+
2553
+ V.namespace = { ...ns };
2639
2554
 
2640
2555
  V.g = g;
2641
2556
 
@@ -0,0 +1,9 @@
1
+ export const svg = 'http://www.w3.org/2000/svg';
2
+
3
+ export const xmlns = 'http://www.w3.org/2000/xmlns/';
4
+
5
+ export const xml = 'http://www.w3.org/XML/1998/namespace';
6
+
7
+ export const xlink = 'http://www.w3.org/1999/xlink';
8
+
9
+ export const xhtml = 'http://www.w3.org/1999/xhtml';
@@ -0,0 +1,183 @@
1
+ import { internalSVGDocument, internalSVGGroup } from './create.mjs';
2
+ import { getCommonAncestor } from './traverse.mjs';
3
+
4
+ /**
5
+ * @returns {SVGMatrix}
6
+ * @description Creates an identity matrix.
7
+ */
8
+ export function createIdentityMatrix() {
9
+ return internalSVGDocument.createSVGMatrix();
10
+ }
11
+
12
+ /**
13
+ * @param {Partial<SVGMatrix>} matrixInit
14
+ * @returns {SVGMatrix}
15
+ * @description Creates a new SVGMatrix object.
16
+ * If no matrix is provided, it returns the identity matrix.
17
+ * If a matrix like object is provided, it sets the matrix values.
18
+ */
19
+ export function createMatrix(matrixInit = {}) {
20
+ const matrix = internalSVGDocument.createSVGMatrix();
21
+ if (!matrixInit) return matrix;
22
+ if ('a' in matrixInit) matrix.a = matrixInit.a;
23
+ if ('b' in matrixInit) matrix.b = matrixInit.b;
24
+ if ('c' in matrixInit) matrix.c = matrixInit.c;
25
+ if ('d' in matrixInit) matrix.d = matrixInit.d;
26
+ if ('e' in matrixInit) matrix.e = matrixInit.e;
27
+ if ('f' in matrixInit) matrix.f = matrixInit.f;
28
+ return matrix;
29
+ }
30
+
31
+ /**
32
+ * @returns {SVGTransform}
33
+ * @description Creates a new SVGTransform object.
34
+ */
35
+ export function createSVGTransform() {
36
+ return internalSVGDocument.createSVGTransform();
37
+ }
38
+
39
+ /**
40
+ * @param {SVGElement} node
41
+ * @returns {SVGMatrix|null}
42
+ * @description Returns the transformation matrix of the given node.
43
+ * If the node has no transformation, it returns null.
44
+ */
45
+ export function getNodeMatrix(node) {
46
+ const consolidatedTransformation = node.transform.baseVal.consolidate();
47
+ return consolidatedTransformation ? consolidatedTransformation.matrix : null;
48
+ }
49
+
50
+ /**
51
+ * @param {string} transformString
52
+ * @returns {SVGMatrix}
53
+ * @description Creates a matrix from the given transform string.
54
+ */
55
+ export function createMatrixFromTransformString(transformString) {
56
+ internalSVGGroup.setAttribute('transform', transformString);
57
+ return getNodeMatrix(internalSVGGroup);
58
+ }
59
+
60
+ /**
61
+ * @param {SVGElement} node
62
+ * @param {Partial<SVGMatrix>} matrixInit
63
+ * @param {boolean} override
64
+ * @description Sets the transformation matrix of the given node.
65
+ * We don't use `node.transform.baseVal` here (@see `transformNode`)
66
+ * for the following reasons:
67
+ * - Performance: while Chrome performs slightly better, Firefox
68
+ * and Safari are significantly slower
69
+ * https://www.measurethat.net/Benchmarks/Show/34447/1/overriding-svg-transform-attribute
70
+ * - Limited support: JSDOM does not support `node.transform.baseVal`
71
+ */
72
+ export function replaceTransformNode(node, matrixInit) {
73
+ node.setAttribute('transform', matrixToTransformString(matrixInit));
74
+ }
75
+
76
+ /**
77
+ * @param {SVGElement} node
78
+ * @param {Partial<SVGMatrix>} matrixInit
79
+ * @description Applies a transformation matrix to the given node.
80
+ * If the node already has a transformation, it appends the new transformation.
81
+ * If the node has no transformation, it creates a new one.
82
+ */
83
+ export function transformNode(node, matrixInit) {
84
+ const transform = createSVGTransform();
85
+ const matrix = isSVGMatrix(matrixInit) ? matrixInit : createMatrix(matrixInit);
86
+ transform.setMatrix(matrix);
87
+ node.transform.baseVal.appendItem(transform);
88
+ }
89
+
90
+ const MATRIX_TYPE = '[object SVGMatrix]';
91
+
92
+ /**
93
+ * @param {any} obj
94
+ * @returns {boolean}
95
+ * @description Checks if the given object is an SVGMatrix.
96
+ */
97
+ export function isSVGMatrix(obj) {
98
+ return Object.prototype.toString.call(obj) === MATRIX_TYPE;
99
+ }
100
+
101
+ /**
102
+ * @param {Partial<SVGMatrix>} matrixInit
103
+ * @returns {string}
104
+ * @description Converts a matrix to a transform string.
105
+ * If no matrix is provided, it returns the identity matrix string.
106
+ */
107
+ export function matrixToTransformString(matrixInit = {}) {
108
+ const { a = 1, b = 0, c = 0, d = 1, e = 0, f = 0 } = matrixInit;
109
+ return `matrix(${a},${b},${c},${d},${e},${f})`;
110
+ }
111
+
112
+ /**
113
+ *
114
+ * @param {SVGElement} a
115
+ * @param {SVGElement} b
116
+ * @returns {SVGMatrix|null}
117
+ * @description Finds the transformation matrix from `a` to `b`.
118
+ * It requires that both elements to be visible (in the render tree)
119
+ * in order to calculate the correct transformation matrix.
120
+ */
121
+ export function getRelativeTransformation(a, b) {
122
+ // Different SVG elements, no transformation possible
123
+ // Note: SVGSVGElement has no `ownerSVGElement`
124
+ if ((a.ownerSVGElement || a) !== (b.ownerSVGElement || b)) return null;
125
+ // Get the transformation matrix from `a` to `b`.
126
+ const am = b.getScreenCTM();
127
+ if (!am) return null;
128
+ const bm = a.getScreenCTM();
129
+ if (!bm) return null;
130
+ return am.inverse().multiply(bm);
131
+ }
132
+
133
+ /**
134
+ * @param {SVGElement} a
135
+ * @param {SVGElement} b
136
+ * @returns {SVGMatrix|null}
137
+ * @description Finds the transformation matrix from `a` to `b`.
138
+ * A safe way to calculate the transformation matrix between two elements.
139
+ * It does not require the elements to be visible (in the render tree).
140
+ */
141
+ export function getRelativeTransformationSafe(a, b) {
142
+ if (a === b) {
143
+ // No transformation needed
144
+ return createIdentityMatrix();
145
+ }
146
+ const position = a.compareDocumentPosition(b);
147
+ if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
148
+ // `b` is a descendant of `a`
149
+ return getLinealTransformation(a, b).inverse();
150
+ } else if (position & Node.DOCUMENT_POSITION_CONTAINS) {
151
+ // `a` is a descendant of `b`
152
+ return getLinealTransformation(b, a);
153
+ }
154
+
155
+ const c = getCommonAncestor(a, b);
156
+ if (!c) {
157
+ // No common ancestor
158
+ return null;
159
+ }
160
+
161
+ const mca = getLinealTransformation(c, a);
162
+ const mcb = getLinealTransformation(c, b);
163
+ return mcb.inverse().multiply(mca);
164
+ }
165
+
166
+ /**
167
+ * @param {SVGElement} descendant
168
+ * @param {SVGElement} ancestor
169
+ * @returns {SVGMatrix}
170
+ * @description Finds the transformation matrix between the `ancestor` and `descendant`.
171
+ */
172
+ function getLinealTransformation(ancestor, descendant) {
173
+ const transformations = [];
174
+ let n = descendant;
175
+ while (n && n.nodeType === Node.ELEMENT_NODE && n !== ancestor) {
176
+ const nm = getNodeMatrix(n);
177
+ if (nm) {
178
+ transformations.unshift(nm);
179
+ }
180
+ n = n.parentNode;
181
+ }
182
+ return transformations.reduce((m, t) => m.multiply(t), createIdentityMatrix());
183
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @param {SVGElement} node1
3
+ * @param {SVGElement} node2
4
+ * @returns {SVGElement|null}
5
+ * @description Finds the common ancestor node of two nodes.
6
+ */
7
+ export function getCommonAncestor(node1, node2) {
8
+ // Find the common ancestor node of two nodes.
9
+ let parent = node1;
10
+ do {
11
+ if (parent.contains(node2)) return parent;
12
+ parent = parent.parentNode;
13
+ } while (parent);
14
+ return null;
15
+ }
16
+