Skip to content

Commit 5adf3c2

Browse files
kumilingusclaude
andauthored
fix(Vectorizer): handle nested SVG in getRelativeTransformation() (clientIO#3176)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e291b8 commit 5adf3c2

2 files changed

Lines changed: 61 additions & 3 deletions

File tree

packages/joint-core/src/V/transform.mjs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ export function matrixToTransformString(matrixInit = {}) {
109109
return `matrix(${a},${b},${c},${d},${e},${f})`;
110110
}
111111

112+
/**
113+
* @param {SVGElement} node
114+
* @returns {SVGSVGElement|null}
115+
* @description Returns the root SVG element for the given node,
116+
* walking up through nested SVG elements.
117+
* Returns `null` if the node is not part of an SVG document.
118+
*/
119+
function getRootSVG(node) {
120+
let svg = node.ownerSVGElement;
121+
if (!svg) {
122+
// The node itself may be an <svg> element
123+
return node instanceof SVGSVGElement ? node : null;
124+
}
125+
while (svg.ownerSVGElement) {
126+
svg = svg.ownerSVGElement;
127+
}
128+
return svg;
129+
}
130+
112131
/**
113132
*
114133
* @param {SVGElement} a
@@ -119,9 +138,12 @@ export function matrixToTransformString(matrixInit = {}) {
119138
* in order to calculate the correct transformation matrix.
120139
*/
121140
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;
141+
// Elements must be part of an SVG document
142+
const rootA = getRootSVG(a);
143+
const rootB = getRootSVG(b);
144+
if (!rootA || !rootB) return null;
145+
// Different SVG documents, no transformation possible
146+
if (rootA !== rootB) return null;
125147
// Get the transformation matrix from `a` to `b`.
126148
const am = b.getScreenCTM();
127149
if (!am) return null;

packages/joint-core/test/vectorizer/vectorizer.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,42 @@ QUnit.module('vectorizer', function(hooks) {
17201720
rect.remove();
17211721
});
17221722

1723+
QUnit.test('nested SVG elements', function(assert) {
1724+
1725+
// SVG > g(A) > g > nested SVG > g > rect(B)
1726+
var elA = V('g', { id: 'outer-g', transform: 'translate(10, 20)' });
1727+
var elB = V('rect', { id: 'inner-rect', width: 10, height: 10 });
1728+
var nestedSvg = V('svg', { id: 'nested-svg', x: '30', y: '40' });
1729+
var innerGroup = V('g', { id: 'inner-g', transform: 'translate(5, 5)' });
1730+
1731+
innerGroup.append(elB);
1732+
nestedSvg.append(innerGroup);
1733+
var outerGroup = V('g');
1734+
outerGroup.append(nestedSvg);
1735+
elA.append(outerGroup);
1736+
1737+
var container = V(svgContainer);
1738+
container.append(elA);
1739+
1740+
var epsilon = 0.01;
1741+
1742+
// getTransformToElement(A, B) maps from A's coordinate space into B's.
1743+
// B is offset from A by nested SVG (x=30,y=40) + inner-g translate(5,5),
1744+
// so the transform into B's space negates that: translate(-35,-45).
1745+
var matrix = elA.getTransformToElement(elB.node);
1746+
assert.ok(matrix, 'Returns a matrix for elements across nested SVG boundary');
1747+
assert.ok(Math.abs(matrix.e - (-35)) < epsilon, 'Matrix tx is ~-35 (got ' + matrix.e + ')');
1748+
assert.ok(Math.abs(matrix.f - (-45)) < epsilon, 'Matrix ty is ~-45 (got ' + matrix.f + ')');
1749+
1750+
// Safe mode (DOM-walk based) should also return a non-null matrix across nested SVG.
1751+
// Note: safe mode does not account for <svg> x/y attributes, only transform attributes.
1752+
var safeMatrix = elA.getTransformToElement(elB.node, { safe: true });
1753+
assert.ok(safeMatrix, 'Safe mode returns a matrix for elements across nested SVG boundary');
1754+
assert.ok(Math.abs(safeMatrix.e - (-5)) < epsilon, 'Safe mode matrix tx is ~-5 (got ' + safeMatrix.e + ')');
1755+
assert.ok(Math.abs(safeMatrix.f - (-5)) < epsilon, 'Safe mode matrix ty is ~-5 (got ' + safeMatrix.f + ')');
1756+
1757+
elA.remove();
1758+
});
17231759

17241760
});
17251761
});

0 commit comments

Comments
 (0)