@svgedit/svgcanvas 7.2.7 → 7.4.2

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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { NS } from './namespaces.js'
9
- import { transformPoint, getMatrix } from './math.js'
9
+ import { transformPoint, matrixMultiply, getTransformList, transformListToTransform } from './math.js'
10
10
  import {
11
11
  assignAttributes,
12
12
  getElement,
@@ -28,68 +28,103 @@ export const init = canvas => {
28
28
  /**
29
29
  * Group: Text edit functions
30
30
  * Functions relating to editing text elements.
31
- * @namespace {PlainObject} textActions
31
+ * @class TextActions
32
32
  * @memberof module:svgcanvas.SvgCanvas#
33
33
  */
34
- export const textActionsMethod = (function () {
35
- let curtext
36
- let textinput
37
- let cursor
38
- let selblock
39
- let blinker
40
- let chardata = []
41
- let textbb // , transbb;
42
- let matrix
43
- let lastX
44
- let lastY
45
- let allowDbl
34
+ class TextActions {
35
+ #curtext = null
36
+ #textinput = null
37
+ #cursor = null
38
+ #selblock = null
39
+ #blinker = null
40
+ #chardata = []
41
+ #textbb = null // , transbb;
42
+ #matrix = null
43
+ #lastX = null
44
+ #lastY = null
45
+ #allowDbl = false
46
+
47
+ /**
48
+ * Get the accumulated transformation matrix from the element up to the SVG content element.
49
+ * This includes transforms from all parent groups, fixing the issue where text cursor
50
+ * appears in the wrong position when editing text inside a transformed group.
51
+ * @param {Element} elem - The element to get the accumulated matrix for
52
+ * @returns {SVGMatrix|null} The accumulated transformation matrix, or null if none
53
+ * @private
54
+ */
55
+ #getAccumulatedMatrix = (elem) => {
56
+ const svgContent = svgCanvas.getSvgContent()
57
+ const matrices = []
58
+
59
+ let current = elem
60
+ while (current && current !== svgContent && current.nodeType === 1) {
61
+ const tlist = getTransformList(current)
62
+ if (tlist && tlist.numberOfItems > 0) {
63
+ const matrix = transformListToTransform(tlist).matrix
64
+ matrices.unshift(matrix) // Add to beginning to maintain correct order
65
+ }
66
+ current = current.parentNode
67
+ }
68
+
69
+ if (matrices.length === 0) {
70
+ return null
71
+ }
72
+
73
+ if (matrices.length === 1) {
74
+ return matrices[0]
75
+ }
76
+
77
+ // Multiply all matrices together
78
+ return matrixMultiply(...matrices)
79
+ }
46
80
 
47
81
  /**
48
82
  *
49
83
  * @param {Integer} index
50
84
  * @returns {void}
85
+ * @private
51
86
  */
52
- function setCursor (index) {
53
- const empty = textinput.value === ''
54
- textinput.focus()
87
+ #setCursor = (index = undefined) => {
88
+ const empty = this.#textinput.value === ''
89
+ this.#textinput.focus()
55
90
 
56
- if (!arguments.length) {
91
+ if (index === undefined) {
57
92
  if (empty) {
58
93
  index = 0
59
94
  } else {
60
- if (textinput.selectionEnd !== textinput.selectionStart) {
95
+ if (this.#textinput.selectionEnd !== this.#textinput.selectionStart) {
61
96
  return
62
97
  }
63
- index = textinput.selectionEnd
98
+ index = this.#textinput.selectionEnd
64
99
  }
65
100
  }
66
101
 
67
- const charbb = chardata[index]
102
+ const charbb = this.#chardata[index]
68
103
  if (!empty) {
69
- textinput.setSelectionRange(index, index)
104
+ this.#textinput.setSelectionRange(index, index)
70
105
  }
71
- cursor = getElement('text_cursor')
72
- if (!cursor) {
73
- cursor = document.createElementNS(NS.SVG, 'line')
74
- assignAttributes(cursor, {
106
+ this.#cursor = getElement('text_cursor')
107
+ if (!this.#cursor) {
108
+ this.#cursor = document.createElementNS(NS.SVG, 'line')
109
+ assignAttributes(this.#cursor, {
75
110
  id: 'text_cursor',
76
111
  stroke: '#333',
77
112
  'stroke-width': 1
78
113
  })
79
- getElement('selectorParentGroup').append(cursor)
114
+ getElement('selectorParentGroup').append(this.#cursor)
80
115
  }
81
116
 
82
- if (!blinker) {
83
- blinker = setInterval(function () {
84
- const show = cursor.getAttribute('display') === 'none'
85
- cursor.setAttribute('display', show ? 'inline' : 'none')
117
+ if (!this.#blinker) {
118
+ this.#blinker = setInterval(() => {
119
+ const show = this.#cursor.getAttribute('display') === 'none'
120
+ this.#cursor.setAttribute('display', show ? 'inline' : 'none')
86
121
  }, 600)
87
122
  }
88
123
 
89
- const startPt = ptToScreen(charbb.x, textbb.y)
90
- const endPt = ptToScreen(charbb.x, textbb.y + textbb.height)
124
+ const startPt = this.#ptToScreen(charbb.x, this.#textbb.y)
125
+ const endPt = this.#ptToScreen(charbb.x, this.#textbb.y + this.#textbb.height)
91
126
 
92
- assignAttributes(cursor, {
127
+ assignAttributes(this.#cursor, {
93
128
  x1: startPt.x,
94
129
  y1: startPt.y,
95
130
  x2: endPt.x,
@@ -98,8 +133,8 @@ export const textActionsMethod = (function () {
98
133
  display: 'inline'
99
134
  })
100
135
 
101
- if (selblock) {
102
- selblock.setAttribute('d', '')
136
+ if (this.#selblock) {
137
+ this.#selblock.setAttribute('d', '')
103
138
  }
104
139
  }
105
140
 
@@ -109,40 +144,41 @@ export const textActionsMethod = (function () {
109
144
  * @param {Integer} end
110
145
  * @param {boolean} skipInput
111
146
  * @returns {void}
147
+ * @private
112
148
  */
113
- function setSelection (start, end, skipInput) {
149
+ #setSelection = (start, end, skipInput) => {
114
150
  if (start === end) {
115
- setCursor(end)
151
+ this.#setCursor(end)
116
152
  return
117
153
  }
118
154
 
119
155
  if (!skipInput) {
120
- textinput.setSelectionRange(start, end)
156
+ this.#textinput.setSelectionRange(start, end)
121
157
  }
122
158
 
123
- selblock = getElement('text_selectblock')
124
- if (!selblock) {
125
- selblock = document.createElementNS(NS.SVG, 'path')
126
- assignAttributes(selblock, {
159
+ this.#selblock = getElement('text_selectblock')
160
+ if (!this.#selblock) {
161
+ this.#selblock = document.createElementNS(NS.SVG, 'path')
162
+ assignAttributes(this.#selblock, {
127
163
  id: 'text_selectblock',
128
164
  fill: 'green',
129
165
  opacity: 0.5,
130
166
  style: 'pointer-events:none'
131
167
  })
132
- getElement('selectorParentGroup').append(selblock)
168
+ getElement('selectorParentGroup').append(this.#selblock)
133
169
  }
134
170
 
135
- const startbb = chardata[start]
136
- const endbb = chardata[end]
171
+ const startbb = this.#chardata[start]
172
+ const endbb = this.#chardata[end]
137
173
 
138
- cursor.setAttribute('visibility', 'hidden')
174
+ this.#cursor.setAttribute('visibility', 'hidden')
139
175
 
140
- const tl = ptToScreen(startbb.x, textbb.y)
141
- const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y)
142
- const bl = ptToScreen(startbb.x, textbb.y + textbb.height)
143
- const br = ptToScreen(
176
+ const tl = this.#ptToScreen(startbb.x, this.#textbb.y)
177
+ const tr = this.#ptToScreen(startbb.x + (endbb.x - startbb.x), this.#textbb.y)
178
+ const bl = this.#ptToScreen(startbb.x, this.#textbb.y + this.#textbb.height)
179
+ const br = this.#ptToScreen(
144
180
  startbb.x + (endbb.x - startbb.x),
145
- textbb.y + textbb.height
181
+ this.#textbb.y + this.#textbb.height
146
182
  )
147
183
 
148
184
  const dstr =
@@ -164,7 +200,7 @@ export const textActionsMethod = (function () {
164
200
  bl.y +
165
201
  'z'
166
202
 
167
- assignAttributes(selblock, {
203
+ assignAttributes(this.#selblock, {
168
204
  d: dstr,
169
205
  display: 'inline'
170
206
  })
@@ -175,29 +211,30 @@ export const textActionsMethod = (function () {
175
211
  * @param {Float} mouseX
176
212
  * @param {Float} mouseY
177
213
  * @returns {Integer}
214
+ * @private
178
215
  */
179
- function getIndexFromPoint (mouseX, mouseY) {
216
+ #getIndexFromPoint = (mouseX, mouseY) => {
180
217
  // Position cursor here
181
218
  const pt = svgCanvas.getSvgRoot().createSVGPoint()
182
219
  pt.x = mouseX
183
220
  pt.y = mouseY
184
221
 
185
222
  // No content, so return 0
186
- if (chardata.length === 1) {
223
+ if (this.#chardata.length === 1) {
187
224
  return 0
188
225
  }
189
226
  // Determine if cursor should be on left or right of character
190
- let charpos = curtext.getCharNumAtPosition(pt)
227
+ let charpos = this.#curtext.getCharNumAtPosition(pt)
191
228
  if (charpos < 0) {
192
229
  // Out of text range, look at mouse coords
193
- charpos = chardata.length - 2
194
- if (mouseX <= chardata[0].x) {
230
+ charpos = this.#chardata.length - 2
231
+ if (mouseX <= this.#chardata[0].x) {
195
232
  charpos = 0
196
233
  }
197
- } else if (charpos >= chardata.length - 2) {
198
- charpos = chardata.length - 2
234
+ } else if (charpos >= this.#chardata.length - 2) {
235
+ charpos = this.#chardata.length - 2
199
236
  }
200
- const charbb = chardata[charpos]
237
+ const charbb = this.#chardata[charpos]
201
238
  const mid = charbb.x + charbb.width / 2
202
239
  if (mouseX > mid) {
203
240
  charpos++
@@ -210,9 +247,10 @@ export const textActionsMethod = (function () {
210
247
  * @param {Float} mouseX
211
248
  * @param {Float} mouseY
212
249
  * @returns {void}
250
+ * @private
213
251
  */
214
- function setCursorFromPoint (mouseX, mouseY) {
215
- setCursor(getIndexFromPoint(mouseX, mouseY))
252
+ #setCursorFromPoint = (mouseX, mouseY) => {
253
+ this.#setCursor(this.#getIndexFromPoint(mouseX, mouseY))
216
254
  }
217
255
 
218
256
  /**
@@ -221,14 +259,15 @@ export const textActionsMethod = (function () {
221
259
  * @param {Float} y
222
260
  * @param {boolean} apply
223
261
  * @returns {void}
262
+ * @private
224
263
  */
225
- function setEndSelectionFromPoint (x, y, apply) {
226
- const i1 = textinput.selectionStart
227
- const i2 = getIndexFromPoint(x, y)
264
+ #setEndSelectionFromPoint = (x, y, apply) => {
265
+ const i1 = this.#textinput.selectionStart
266
+ const i2 = this.#getIndexFromPoint(x, y)
228
267
 
229
268
  const start = Math.min(i1, i2)
230
269
  const end = Math.max(i1, i2)
231
- setSelection(start, end, !apply)
270
+ this.#setSelection(start, end, !apply)
232
271
  }
233
272
 
234
273
  /**
@@ -236,8 +275,9 @@ export const textActionsMethod = (function () {
236
275
  * @param {Float} xIn
237
276
  * @param {Float} yIn
238
277
  * @returns {module:math.XYObject}
278
+ * @private
239
279
  */
240
- function screenToPt (xIn, yIn) {
280
+ #screenToPt = (xIn, yIn) => {
241
281
  const out = {
242
282
  x: xIn,
243
283
  y: yIn
@@ -246,8 +286,8 @@ export const textActionsMethod = (function () {
246
286
  out.x /= zoom
247
287
  out.y /= zoom
248
288
 
249
- if (matrix) {
250
- const pt = transformPoint(out.x, out.y, matrix.inverse())
289
+ if (this.#matrix) {
290
+ const pt = transformPoint(out.x, out.y, this.#matrix.inverse())
251
291
  out.x = pt.x
252
292
  out.y = pt.y
253
293
  }
@@ -260,15 +300,16 @@ export const textActionsMethod = (function () {
260
300
  * @param {Float} xIn
261
301
  * @param {Float} yIn
262
302
  * @returns {module:math.XYObject}
303
+ * @private
263
304
  */
264
- function ptToScreen (xIn, yIn) {
305
+ #ptToScreen = (xIn, yIn) => {
265
306
  const out = {
266
307
  x: xIn,
267
308
  y: yIn
268
309
  }
269
310
 
270
- if (matrix) {
271
- const pt = transformPoint(out.x, out.y, matrix)
311
+ if (this.#matrix) {
312
+ const pt = transformPoint(out.x, out.y, this.#matrix)
272
313
  out.x = pt.x
273
314
  out.y = pt.y
274
315
  }
@@ -283,279 +324,294 @@ export const textActionsMethod = (function () {
283
324
  *
284
325
  * @param {Event} evt
285
326
  * @returns {void}
327
+ * @private
286
328
  */
287
- function selectAll (evt) {
288
- setSelection(0, curtext.textContent.length)
289
- evt.target.removeEventListener('click', selectAll)
329
+ #selectAll = (evt) => {
330
+ this.#setSelection(0, this.#curtext.textContent.length)
331
+ evt.target.removeEventListener('click', this.#selectAll)
290
332
  }
291
333
 
292
334
  /**
293
335
  *
294
336
  * @param {Event} evt
295
337
  * @returns {void}
338
+ * @private
296
339
  */
297
- function selectWord (evt) {
298
- if (!allowDbl || !curtext) {
340
+ #selectWord = (evt) => {
341
+ if (!this.#allowDbl || !this.#curtext) {
299
342
  return
300
343
  }
301
344
  const zoom = svgCanvas.getZoom()
302
345
  const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm())
303
346
  const mouseX = ept.x * zoom
304
347
  const mouseY = ept.y * zoom
305
- const pt = screenToPt(mouseX, mouseY)
348
+ const pt = this.#screenToPt(mouseX, mouseY)
306
349
 
307
- const index = getIndexFromPoint(pt.x, pt.y)
308
- const str = curtext.textContent
309
- const first = str.substr(0, index).replace(/[a-z\d]+$/i, '').length
310
- const m = str.substr(index).match(/^[a-z\d]+/i)
350
+ const index = this.#getIndexFromPoint(pt.x, pt.y)
351
+ const str = this.#curtext.textContent
352
+ const first = str.slice(0, index).replace(/[a-z\d]+$/i, '').length
353
+ const m = str.slice(index).match(/^[a-z\d]+/i)
311
354
  const last = (m ? m[0].length : 0) + index
312
- setSelection(first, last)
355
+ this.#setSelection(first, last)
313
356
 
314
357
  // Set tripleclick
315
- svgCanvas.$click(evt.target, selectAll)
358
+ svgCanvas.$click(evt.target, this.#selectAll)
316
359
 
317
- setTimeout(function () {
318
- evt.target.removeEventListener('click', selectAll)
360
+ setTimeout(() => {
361
+ evt.target.removeEventListener('click', this.#selectAll)
319
362
  }, 300)
320
363
  }
321
364
 
322
- return /** @lends module:svgcanvas.SvgCanvas#textActions */ {
323
- /**
324
- * @param {Element} target
325
- * @param {Float} x
326
- * @param {Float} y
327
- * @returns {void}
328
- */
329
- select (target, x, y) {
330
- curtext = target
331
- svgCanvas.textActions.toEditMode(x, y)
332
- },
333
- /**
334
- * @param {Element} elem
335
- * @returns {void}
336
- */
337
- start (elem) {
338
- curtext = elem
339
- svgCanvas.textActions.toEditMode()
340
- },
341
- /**
342
- * @param {external:MouseEvent} evt
343
- * @param {Element} mouseTarget
344
- * @param {Float} startX
345
- * @param {Float} startY
346
- * @returns {void}
347
- */
348
- mouseDown (evt, mouseTarget, startX, startY) {
349
- const pt = screenToPt(startX, startY)
350
-
351
- textinput.focus()
352
- setCursorFromPoint(pt.x, pt.y)
353
- lastX = startX
354
- lastY = startY
355
-
356
- // TODO: Find way to block native selection
357
- },
358
- /**
359
- * @param {Float} mouseX
360
- * @param {Float} mouseY
361
- * @returns {void}
362
- */
363
- mouseMove (mouseX, mouseY) {
364
- const pt = screenToPt(mouseX, mouseY)
365
- setEndSelectionFromPoint(pt.x, pt.y)
366
- },
367
- /**
368
- * @param {external:MouseEvent} evt
369
- * @param {Float} mouseX
370
- * @param {Float} mouseY
371
- * @returns {void}
372
- */
373
- mouseUp (evt, mouseX, mouseY) {
374
- const pt = screenToPt(mouseX, mouseY)
375
-
376
- setEndSelectionFromPoint(pt.x, pt.y, true)
377
-
378
- // TODO: Find a way to make this work: Use transformed BBox instead of evt.target
379
- // if (lastX === mouseX && lastY === mouseY
380
- // && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) {
381
- // svgCanvas.textActions.toSelectMode(true);
382
- // }
383
-
384
- if (
385
- evt.target !== curtext &&
386
- mouseX < lastX + 2 &&
387
- mouseX > lastX - 2 &&
388
- mouseY < lastY + 2 &&
389
- mouseY > lastY - 2
390
- ) {
391
- svgCanvas.textActions.toSelectMode(true)
392
- }
393
- },
394
- /**
395
- * @function
396
- * @param {Integer} index
397
- * @returns {void}
398
- */
399
- setCursor,
400
- /**
401
- * @param {Float} x
402
- * @param {Float} y
403
- * @returns {void}
404
- */
405
- toEditMode (x, y) {
406
- allowDbl = false
407
- svgCanvas.setCurrentMode('textedit')
408
- svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
409
- // Make selector group accept clicks
410
- /* const selector = */ svgCanvas.selectorManager.requestSelector(curtext) // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
411
- // const sel = selector.selectorRect;
412
-
413
- svgCanvas.textActions.init()
414
-
415
- curtext.style.cursor = 'text'
416
-
417
- // if (supportsEditableText()) {
418
- // curtext.setAttribute('editable', 'simple');
419
- // return;
420
- // }
421
-
422
- if (!arguments.length) {
423
- setCursor()
424
- } else {
425
- const pt = screenToPt(x, y)
426
- setCursorFromPoint(pt.x, pt.y)
427
- }
365
+ /**
366
+ * @param {Element} target
367
+ * @param {Float} x
368
+ * @param {Float} y
369
+ * @returns {void}
370
+ */
371
+ select (target, x, y) {
372
+ this.#curtext = target
373
+ svgCanvas.textActions.toEditMode(x, y)
374
+ }
428
375
 
429
- setTimeout(function () {
430
- allowDbl = true
431
- }, 300)
432
- },
433
- /**
434
- * @param {boolean|Element} selectElem
435
- * @fires module:svgcanvas.SvgCanvas#event:selected
436
- * @returns {void}
437
- */
438
- toSelectMode (selectElem) {
439
- svgCanvas.setCurrentMode('select')
440
- clearInterval(blinker)
441
- blinker = null
442
- if (selblock) {
443
- selblock.setAttribute('display', 'none')
444
- }
445
- if (cursor) {
446
- cursor.setAttribute('visibility', 'hidden')
447
- }
448
- curtext.style.cursor = 'move'
376
+ /**
377
+ * @param {Element} elem
378
+ * @returns {void}
379
+ */
380
+ start (elem) {
381
+ this.#curtext = elem
382
+ svgCanvas.textActions.toEditMode()
383
+ }
449
384
 
450
- if (selectElem) {
451
- svgCanvas.clearSelection()
452
- curtext.style.cursor = 'move'
385
+ /**
386
+ * @param {external:MouseEvent} evt
387
+ * @param {Element} mouseTarget
388
+ * @param {Float} startX
389
+ * @param {Float} startY
390
+ * @returns {void}
391
+ */
392
+ mouseDown (evt, mouseTarget, startX, startY) {
393
+ const pt = this.#screenToPt(startX, startY)
453
394
 
454
- svgCanvas.call('selected', [curtext])
455
- svgCanvas.addToSelection([curtext], true)
456
- }
457
- if (!curtext?.textContent.length) {
458
- // No content, so delete
459
- svgCanvas.deleteSelectedElements()
460
- }
395
+ this.#textinput.focus()
396
+ this.#setCursorFromPoint(pt.x, pt.y)
397
+ this.#lastX = startX
398
+ this.#lastY = startY
461
399
 
462
- textinput.blur()
463
-
464
- curtext = false
465
-
466
- // if (supportsEditableText()) {
467
- // curtext.removeAttribute('editable');
468
- // }
469
- },
470
- /**
471
- * @param {Element} elem
472
- * @returns {void}
473
- */
474
- setInputElem (elem) {
475
- textinput = elem
476
- },
477
- /**
478
- * @returns {void}
479
- */
480
- clear () {
481
- if (svgCanvas.getCurrentMode() === 'textedit') {
482
- svgCanvas.textActions.toSelectMode()
483
- }
484
- },
485
- /**
486
- * @param {Element} _inputElem Not in use
487
- * @returns {void}
488
- */
489
- init (_inputElem) {
490
- if (!curtext) {
491
- return
492
- }
493
- let i
494
- let end
495
- // if (supportsEditableText()) {
496
- // curtext.select();
497
- // return;
498
- // }
499
-
500
- if (!curtext.parentNode) {
501
- // Result of the ffClone, need to get correct element
502
- const selectedElements = svgCanvas.getSelectedElements()
503
- curtext = selectedElements[0]
504
- svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
505
- }
400
+ // TODO: Find way to block native selection
401
+ }
402
+
403
+ /**
404
+ * @param {Float} mouseX
405
+ * @param {Float} mouseY
406
+ * @returns {void}
407
+ */
408
+ mouseMove (mouseX, mouseY) {
409
+ const pt = this.#screenToPt(mouseX, mouseY)
410
+ this.#setEndSelectionFromPoint(pt.x, pt.y)
411
+ }
412
+
413
+ /**
414
+ * @param {external:MouseEvent} evt
415
+ * @param {Float} mouseX
416
+ * @param {Float} mouseY
417
+ * @returns {void}
418
+ */
419
+ mouseUp (evt, mouseX, mouseY) {
420
+ const pt = this.#screenToPt(mouseX, mouseY)
421
+
422
+ this.#setEndSelectionFromPoint(pt.x, pt.y, true)
423
+
424
+ // TODO: Find a way to make this work: Use transformed BBox instead of evt.target
425
+ // if (lastX === mouseX && lastY === mouseY
426
+ // && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) {
427
+ // svgCanvas.textActions.toSelectMode(true);
428
+ // }
429
+
430
+ if (
431
+ evt.target !== this.#curtext &&
432
+ mouseX < this.#lastX + 2 &&
433
+ mouseX > this.#lastX - 2 &&
434
+ mouseY < this.#lastY + 2 &&
435
+ mouseY > this.#lastY - 2
436
+ ) {
437
+ svgCanvas.textActions.toSelectMode(true)
438
+ }
439
+ }
440
+
441
+ /**
442
+ * @param {Integer} index
443
+ * @returns {void}
444
+ */
445
+ setCursor (index) {
446
+ this.#setCursor(index)
447
+ }
506
448
 
507
- const str = curtext.textContent
508
- const len = str.length
449
+ /**
450
+ * @param {Float} x
451
+ * @param {Float} y
452
+ * @returns {void}
453
+ */
454
+ toEditMode (x, y) {
455
+ this.#allowDbl = false
456
+ svgCanvas.setCurrentMode('textedit')
457
+ svgCanvas.selectorManager.requestSelector(this.#curtext).showGrips(false)
458
+ // Make selector group accept clicks
459
+ /* const selector = */ svgCanvas.selectorManager.requestSelector(this.#curtext) // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
460
+ // const sel = selector.selectorRect;
461
+
462
+ svgCanvas.textActions.init()
463
+
464
+ this.#curtext.style.cursor = 'text'
465
+
466
+ // if (supportsEditableText()) {
467
+ // curtext.setAttribute('editable', 'simple');
468
+ // return;
469
+ // }
470
+
471
+ if (arguments.length === 0) {
472
+ this.#setCursor()
473
+ } else {
474
+ const pt = this.#screenToPt(x, y)
475
+ this.#setCursorFromPoint(pt.x, pt.y)
476
+ }
509
477
 
510
- const xform = curtext.getAttribute('transform')
478
+ setTimeout(() => {
479
+ this.#allowDbl = true
480
+ }, 300)
481
+ }
511
482
 
512
- textbb = utilsGetBBox(curtext)
483
+ /**
484
+ * @param {boolean|Element} selectElem
485
+ * @fires module:svgcanvas.SvgCanvas#event:selected
486
+ * @returns {void}
487
+ */
488
+ toSelectMode (selectElem) {
489
+ svgCanvas.setCurrentMode('select')
490
+ clearInterval(this.#blinker)
491
+ this.#blinker = null
492
+ if (this.#selblock) {
493
+ this.#selblock.setAttribute('display', 'none')
494
+ }
495
+ if (this.#cursor) {
496
+ this.#cursor.setAttribute('visibility', 'hidden')
497
+ }
498
+ this.#curtext.style.cursor = 'move'
513
499
 
514
- matrix = xform ? getMatrix(curtext) : null
500
+ if (selectElem) {
501
+ svgCanvas.clearSelection()
502
+ this.#curtext.style.cursor = 'move'
515
503
 
516
- chardata = []
517
- chardata.length = len
518
- textinput.focus()
504
+ svgCanvas.call('selected', [this.#curtext])
505
+ svgCanvas.addToSelection([this.#curtext], true)
506
+ }
507
+ if (!this.#curtext?.textContent.length) {
508
+ // No content, so delete
509
+ svgCanvas.deleteSelectedElements()
510
+ }
519
511
 
520
- curtext.removeEventListener('dblclick', selectWord)
521
- curtext.addEventListener('dblclick', selectWord)
512
+ this.#textinput.blur()
522
513
 
523
- if (!len) {
524
- end = { x: textbb.x + textbb.width / 2, width: 0 }
525
- }
514
+ this.#curtext = false
526
515
 
527
- for (i = 0; i < len; i++) {
528
- const start = curtext.getStartPositionOfChar(i)
529
- end = curtext.getEndPositionOfChar(i)
516
+ // if (supportsEditableText()) {
517
+ // curtext.removeAttribute('editable');
518
+ // }
519
+ }
530
520
 
531
- if (!supportsGoodTextCharPos()) {
532
- const zoom = svgCanvas.getZoom()
533
- const offset = svgCanvas.contentW * zoom
534
- start.x -= offset
535
- end.x -= offset
521
+ /**
522
+ * @param {Element} elem
523
+ * @returns {void}
524
+ */
525
+ setInputElem (elem) {
526
+ this.#textinput = elem
527
+ }
536
528
 
537
- start.x /= zoom
538
- end.x /= zoom
539
- }
529
+ /**
530
+ * @returns {void}
531
+ */
532
+ clear () {
533
+ if (svgCanvas.getCurrentMode() === 'textedit') {
534
+ svgCanvas.textActions.toSelectMode()
535
+ }
536
+ }
540
537
 
541
- // Get a "bbox" equivalent for each character. Uses the
542
- // bbox data of the actual text for y, height purposes
538
+ /**
539
+ * @param {Element} _inputElem Not in use
540
+ * @returns {void}
541
+ */
542
+ init (_inputElem) {
543
+ if (!this.#curtext) {
544
+ return
545
+ }
546
+ let i
547
+ let end
548
+ // if (supportsEditableText()) {
549
+ // curtext.select();
550
+ // return;
551
+ // }
552
+
553
+ if (!this.#curtext.parentNode) {
554
+ // Result of the ffClone, need to get correct element
555
+ const selectedElements = svgCanvas.getSelectedElements()
556
+ this.#curtext = selectedElements[0]
557
+ svgCanvas.selectorManager.requestSelector(this.#curtext).showGrips(false)
558
+ }
543
559
 
544
- // TODO: Decide if y, width and height are actually necessary
545
- chardata[i] = {
546
- x: start.x,
547
- y: textbb.y, // start.y?
548
- width: end.x - start.x,
549
- height: textbb.height
550
- }
560
+ const str = this.#curtext.textContent
561
+ const len = str.length
562
+
563
+ this.#textbb = utilsGetBBox(this.#curtext)
564
+
565
+ // Calculate accumulated transform matrix including all parent groups
566
+ // This fixes the issue where text cursor appears in wrong position
567
+ // when editing text inside a group with transforms
568
+ this.#matrix = this.#getAccumulatedMatrix(this.#curtext)
569
+
570
+ this.#chardata = []
571
+ this.#chardata.length = len
572
+ this.#textinput.focus()
573
+
574
+ this.#curtext.removeEventListener('dblclick', this.#selectWord)
575
+ this.#curtext.addEventListener('dblclick', this.#selectWord)
576
+
577
+ if (!len) {
578
+ end = { x: this.#textbb.x + this.#textbb.width / 2, width: 0 }
579
+ }
580
+
581
+ for (i = 0; i < len; i++) {
582
+ const start = this.#curtext.getStartPositionOfChar(i)
583
+ end = this.#curtext.getEndPositionOfChar(i)
584
+
585
+ if (!supportsGoodTextCharPos()) {
586
+ const zoom = svgCanvas.getZoom()
587
+ const offset = svgCanvas.contentW * zoom
588
+ start.x -= offset
589
+ end.x -= offset
590
+
591
+ start.x /= zoom
592
+ end.x /= zoom
551
593
  }
552
594
 
553
- // Add a last bbox for cursor at end of text
554
- chardata.push({
555
- x: end.x,
556
- width: 0
557
- })
558
- setSelection(textinput.selectionStart, textinput.selectionEnd, true)
595
+ // Get a "bbox" equivalent for each character. Uses the
596
+ // bbox data of the actual text for y, height purposes
597
+
598
+ // TODO: Decide if y, width and height are actually necessary
599
+ this.#chardata[i] = {
600
+ x: start.x,
601
+ y: this.#textbb.y, // start.y?
602
+ width: end.x - start.x,
603
+ height: this.#textbb.height
604
+ }
559
605
  }
606
+
607
+ // Add a last bbox for cursor at end of text
608
+ this.#chardata.push({
609
+ x: end.x,
610
+ width: 0
611
+ })
612
+ this.#setSelection(this.#textinput.selectionStart, this.#textinput.selectionEnd, true)
560
613
  }
561
- })()
614
+ }
615
+
616
+ // Export singleton instance for backward compatibility
617
+ export const textActionsMethod = new TextActions()