fix(text-actions): Calculate accumulated transform matrix for text inside groups (#1082)
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:
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user