fix(text-actions): Calculate accumulated transform matrix for text inside groups (#1082)
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
check each push / build (push) Has been cancelled

When editing text inside a group element, the text cursor was appearing
in the wrong position because only the text element's own transform was
being considered, ignoring transforms from parent groups.

This change:
- Adds a new #getAccumulatedMatrix() method that traverses up the DOM tree
  from the text element to the SVG content element, collecting and multiplying
  all transform matrices along the way
- Updates the init() method to use this accumulated matrix instead of just
  the text element's transform
- Updates test mock to include getSvgContent() method

Fixes the issue where editing text inside a transformed group would show
the cursor at the wrong position.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Namhoon Lee
2026-02-04 22:57:35 +09:00
committed by GitHub
parent b9149f73cb
commit 0be0c3916c
2 changed files with 46 additions and 5 deletions

View File

@@ -6,7 +6,7 @@
*/
import { NS } from './namespaces.js'
import { transformPoint, getMatrix } from './math.js'
import { transformPoint, matrixMultiply, getTransformList, transformListToTransform } from './math.js'
import {
assignAttributes,
getElement,
@@ -44,6 +44,40 @@ class TextActions {
#lastY = null
#allowDbl = false
/**
* Get the accumulated transformation matrix from the element up to the SVG content element.
* This includes transforms from all parent groups, fixing the issue where text cursor
* appears in the wrong position when editing text inside a transformed group.
* @param {Element} elem - The element to get the accumulated matrix for
* @returns {SVGMatrix|null} The accumulated transformation matrix, or null if none
* @private
*/
#getAccumulatedMatrix = (elem) => {
const svgContent = svgCanvas.getSvgContent()
const matrices = []
let current = elem
while (current && current !== svgContent && current.nodeType === 1) {
const tlist = getTransformList(current)
if (tlist && tlist.numberOfItems > 0) {
const matrix = transformListToTransform(tlist).matrix
matrices.unshift(matrix) // Add to beginning to maintain correct order
}
current = current.parentNode
}
if (matrices.length === 0) {
return null
}
if (matrices.length === 1) {
return matrices[0]
}
// Multiply all matrices together
return matrixMultiply(...matrices)
}
/**
*
* @param {Integer} index
@@ -526,11 +560,12 @@ class TextActions {
const str = this.#curtext.textContent
const len = str.length
const xform = this.#curtext.getAttribute('transform')
this.#textbb = utilsGetBBox(this.#curtext)
this.#matrix = xform ? getMatrix(this.#curtext) : null
// Calculate accumulated transform matrix including all parent groups
// This fixes the issue where text cursor appears in wrong position
// when editing text inside a group with transforms
this.#matrix = this.#getAccumulatedMatrix(this.#curtext)
this.#chardata = []
this.#chardata.length = len

View File

@@ -18,12 +18,17 @@ describe('TextActions', () => {
svgRoot.setAttribute('height', '480')
document.body.append(svgRoot)
// Create svgContent element (container for SVG content)
const svgContent = document.createElementNS(NS.SVG, 'svg')
svgContent.id = 'svgcontent'
svgRoot.append(svgContent)
textElement = document.createElementNS(NS.SVG, 'text')
textElement.setAttribute('x', '100')
textElement.setAttribute('y', '100')
textElement.setAttribute('id', 'text1')
textElement.textContent = 'Test'
svgRoot.append(textElement)
svgContent.append(textElement)
// Mock text measurement methods
textElement.getStartPositionOfChar = vi.fn((i) => ({ x: 100 + i * 10, y: 100 }))
@@ -55,6 +60,7 @@ describe('TextActions', () => {
// Mock svgCanvas
svgCanvas = {
getSvgRoot: () => svgRoot,
getSvgContent: () => svgContent,
getZoom: () => 1,
setCurrentMode: vi.fn(),
clearSelection: vi.fn(),