@elixpo/lixsketch 5.5.9 → 5.5.10

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 (25) hide show
  1. package/dist/react/{CopyPaste-4CKJRHJ3.js → CopyPaste-SA72NDFN.js} +2 -2
  2. package/dist/react/{EventDispatcher-AD2BPPPS.js → EventDispatcher-BZQRHROG.js} +2 -2
  3. package/dist/react/{SceneSerializer-4U4B24JM.js → SceneSerializer-ZPHV6IUP.js} +2 -2
  4. package/dist/react/{SketchEngine-XWWTA2MT.js → SketchEngine-BCRJ62CV.js} +6 -6
  5. package/dist/react/TextShape-U6ZZKB35.js +8 -0
  6. package/dist/react/{chunk-QXFBG7OM.js → chunk-42O6W7DF.js} +18 -1
  7. package/dist/react/chunk-42O6W7DF.js.map +7 -0
  8. package/dist/react/{chunk-YVCENC7J.js → chunk-5QIT2WBE.js} +19 -116
  9. package/dist/react/chunk-5QIT2WBE.js.map +7 -0
  10. package/dist/react/index.js +3 -3
  11. package/dist/react/index.js.map +1 -1
  12. package/dist/react/{textTool-KY4AYUTC.js → textTool-HY5NMM7H.js} +2 -2
  13. package/package.json +1 -1
  14. package/src/react/components/sidebars/TextSidebar.jsx +1 -1
  15. package/src/shapes/TextShape.js +19 -0
  16. package/src/tools/textTool.js +23 -157
  17. package/dist/react/TextShape-54ZJ66E3.js +0 -8
  18. package/dist/react/chunk-QXFBG7OM.js.map +0 -7
  19. package/dist/react/chunk-YVCENC7J.js.map +0 -7
  20. /package/dist/react/{CopyPaste-4CKJRHJ3.js.map → CopyPaste-SA72NDFN.js.map} +0 -0
  21. /package/dist/react/{EventDispatcher-AD2BPPPS.js.map → EventDispatcher-BZQRHROG.js.map} +0 -0
  22. /package/dist/react/{SceneSerializer-4U4B24JM.js.map → SceneSerializer-ZPHV6IUP.js.map} +0 -0
  23. /package/dist/react/{SketchEngine-XWWTA2MT.js.map → SketchEngine-BCRJ62CV.js.map} +0 -0
  24. /package/dist/react/{TextShape-54ZJ66E3.js.map → TextShape-U6ZZKB35.js.map} +0 -0
  25. /package/dist/react/{textTool-KY4AYUTC.js.map → textTool-HY5NMM7H.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  handleTextMouseMove,
7
7
  handleTextMouseUp,
8
8
  updateCodeToggleForShape
9
- } from "./chunk-YVCENC7J.js";
9
+ } from "./chunk-5QIT2WBE.js";
10
10
  import "./chunk-LHRTXNVE.js";
11
11
  import "./chunk-BZE4CEBG.js";
12
12
  import "./chunk-L5OARZJ7.js";
@@ -18,4 +18,4 @@ export {
18
18
  handleTextMouseUp,
19
19
  updateCodeToggleForShape
20
20
  };
21
- //# sourceMappingURL=textTool-KY4AYUTC.js.map
21
+ //# sourceMappingURL=textTool-HY5NMM7H.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixpo/lixsketch",
3
- "version": "5.5.9",
3
+ "version": "5.5.10",
4
4
  "description": "Open-source SVG whiteboard engine + React canvas component with hand-drawn aesthetics — the core of LixSketch",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,7 +27,7 @@ export default function TextSidebar() {
27
27
  const activeTool = useSketchStore((s) => s.activeTool)
28
28
  const selectedShapeSidebar = useSketchStore((s) => s.selectedShapeSidebar)
29
29
  const [textColor, setTextColor] = useState('#fff')
30
- const [fontSize, setFontSize] = useState('M')
30
+ const [fontSize, setFontSize] = useState('S')
31
31
  const [font, setFont] = useState('lixFont')
32
32
  const [codeMode, setCodeMode] = useState(false)
33
33
  const [language, setLanguage] = useState('javascript')
@@ -25,9 +25,28 @@ function updateSelectionFeedback() {}
25
25
  function deselectElement() { selectedElement = null; }
26
26
  function selectElement(el) { selectedElement = el; }
27
27
 
28
+ function removeLegacySoftWrap(groupElement) {
29
+ const textElement = groupElement.querySelector('text');
30
+ if (!textElement || !textElement.hasAttribute('data-wrap-width')) return;
31
+ const source = textElement.getAttribute('data-wrap-source');
32
+ if (source === null) return;
33
+
34
+ while (textElement.firstChild) textElement.removeChild(textElement.firstChild);
35
+ const x = textElement.getAttribute('x') || 0;
36
+ source.split('\n').forEach((line, index) => {
37
+ const tspan = textElement.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'tspan');
38
+ tspan.setAttribute('x', x);
39
+ tspan.setAttribute('dy', index === 0 ? '0' : '1.2em');
40
+ tspan.textContent = line || ' ';
41
+ textElement.appendChild(tspan);
42
+ });
43
+ textElement.removeAttribute('data-wrap-width');
44
+ }
45
+
28
46
  class TextShape {
29
47
  constructor(groupElement) {
30
48
  this.group = groupElement;
49
+ removeLegacySoftWrap(groupElement);
31
50
  this.shapeName = 'text';
32
51
  this.shapeID = groupElement.getAttribute('id') || `text-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;
33
52
 
@@ -27,7 +27,7 @@ import {
27
27
  getSelectedCodeBlock
28
28
  } from './codeTool.js';
29
29
 
30
- let textSize = "30px";
30
+ let textSize = "20px";
31
31
  let textFont = "lixFont";
32
32
  let textColor = null;
33
33
  let textAlign = "left";
@@ -196,10 +196,8 @@ function makeTextEditable(textElement, groupElement) {
196
196
  input.setAttribute("aria-label", "Edit canvas text");
197
197
  input.setAttribute("data-editing", "true");
198
198
 
199
- // Issue #48 phase D follow-up: prefer the persisted `data-wrap-
200
- // source` when present so re-opening the editor shows the user's
201
- // original line breaks instead of the auto-wrap output. Falls back
202
- // to tspan-concatenation for legacy text shapes.
199
+ // Prefer the persisted source so re-opening the editor preserves only
200
+ // the line breaks the user explicitly inserted with Shift+Enter.
203
201
  let textContent = '';
204
202
  const storedSource = textElement.getAttribute('data-wrap-source');
205
203
  if (storedSource !== null && storedSource !== '') {
@@ -224,12 +222,10 @@ function makeTextEditable(textElement, groupElement) {
224
222
  input.style.boxSizing = "border-box";
225
223
  input.style.overflow = "hidden";
226
224
  input.style.resize = "none";
227
- input.style.whiteSpace = "pre-wrap";
225
+ input.style.whiteSpace = "pre";
228
226
  input.style.minHeight = "1.2em";
229
227
  input.style.zIndex = "10000";
230
228
 
231
- const svgRect = svg.getBoundingClientRect();
232
-
233
229
  // Use the group element's own screenCTM which includes group transform + SVG viewBox transform
234
230
  const textBBox = textElement.getBBox();
235
231
  let pt = svg.createSVGPoint();
@@ -243,17 +239,15 @@ function makeTextEditable(textElement, groupElement) {
243
239
  input.style.top = `${screenPt.y}px`;
244
240
 
245
241
  const svgZoomFactor = svg.getScreenCTM() ? svg.getScreenCTM().a : 1;
246
- const screenWidth = textBBox.width * svgZoomFactor;
247
-
248
242
  input.style.width = "auto";
249
243
  input.style.height = "auto";
250
244
 
251
- const currentFontSize = textElement.getAttribute("font-size") || "30px";
245
+ const currentFontSize = textElement.getAttribute("font-size") || "20px";
252
246
  const currentFontFamily = textElement.getAttribute("font-family") || "lixFont";
253
247
  const currentFill = textElement.getAttribute("fill") || "#fff";
254
248
  const currentAnchor = textElement.getAttribute("text-anchor") || "start";
255
249
  // Scale font-size by zoom so the textarea matches what the user sees on canvas
256
- const rawSize = parseFloat(currentFontSize) || 30;
250
+ const rawSize = parseFloat(currentFontSize) || 20;
257
251
  const scaledFontSize = `${rawSize * svgZoomFactor}px`;
258
252
 
259
253
  input.style.minWidth = "150px";
@@ -261,53 +255,31 @@ function makeTextEditable(textElement, groupElement) {
261
255
  input.style.width = "auto";
262
256
  input.style.height = "auto";
263
257
  input.style.overflow = "visible";
264
- input.style.whiteSpace = "pre-wrap";
265
- input.style.wordBreak = "break-word";
258
+ input.style.whiteSpace = "pre";
259
+ input.style.wordBreak = "normal";
260
+ input.style.overflowWrap = "normal";
266
261
  input.style.fontSize = scaledFontSize;
267
262
  input.style.fontFamily = currentFontFamily;
268
263
  input.style.color = currentFill;
269
264
  input.style.lineHeight = "1.2em";
270
265
  input.style.textAlign = currentAnchor === "middle" ? "center" : currentAnchor === "end" ? "right" : "left";
271
266
  input.style.backgroundColor = "transparent";
272
- // Keep an obvious editing-only bounding box around the textarea. The
273
- // editor is removed by renderText(), so this never becomes part of the
274
- // rendered canvas or exported scene.
275
- const _isDark = typeof document !== 'undefined'
276
- && document.body
277
- && document.body.classList.contains('theme-dark');
278
- const editingBorder = _isDark ? "rgba(139,136,232,0.95)" : "rgba(91,87,209,0.92)";
279
- input.style.border = `2px solid ${editingBorder}`;
280
- input.style.backgroundColor = _isDark
281
- ? "rgba(91,87,209,0.08)"
282
- : "rgba(91,87,209,0.05)";
283
- input.style.boxShadow = `0 0 0 2px ${_isDark ? "rgba(139,136,232,0.18)" : "rgba(91,87,209,0.14)"}`;
284
- input.style.borderRadius = "4px";
267
+ input.style.border = "none";
268
+ input.style.boxShadow = "none";
285
269
  input.style.outline = "none";
286
270
  document.body.appendChild(input);
287
271
 
288
272
  const adjustHeight = () => {
289
273
  input.style.height = 'auto';
290
274
  input.style.height = input.scrollHeight + 'px';
291
- const maxHeight = svgRect.height - (screenPt.y);
292
- if (input.scrollHeight > maxHeight) {
293
- input.style.height = maxHeight + 'px';
294
- input.style.overflowY = 'auto';
295
- } else {
296
- input.style.overflowY = 'hidden';
297
- }
275
+ input.style.overflowY = 'hidden';
298
276
  };
299
277
 
300
278
  const adjustWidth = () => {
301
279
  input.style.width = 'auto';
302
- const maxWidth = svgRect.width - (screenPt.x);
303
280
  const contentWidth = Math.max(input.scrollWidth, 150);
304
- if (contentWidth > maxWidth) {
305
- input.style.width = maxWidth + 'px';
306
- input.style.overflowX = 'auto';
307
- } else {
308
- input.style.width = contentWidth + 'px';
309
- input.style.overflowX = 'hidden';
310
- }
281
+ input.style.width = contentWidth + 'px';
282
+ input.style.overflowX = 'hidden';
311
283
  };
312
284
  adjustHeight();
313
285
  adjustWidth();
@@ -349,58 +321,12 @@ function makeTextEditable(textElement, groupElement) {
349
321
  groupElement.style.display = "none";
350
322
  }
351
323
 
352
- /**
353
- * Issue #48 phase D follow-up: true text wrapping driven by E/W width.
354
- *
355
- * `paint(textElement, source, wrapWidth)` renders the user-typed `source`
356
- * string into tspans, soft-wrapping any line that exceeds `wrapWidth`
357
- * pixels (in SVG units). `wrapWidth = null | 0` falls back to the old
358
- * "explicit newlines only" behaviour so existing text shapes that have
359
- * never been width-resized look identical.
360
- *
361
- * The raw `source` is persisted on the element as `data-wrap-source`
362
- * so subsequent edits (textarea re-open) and serialisation can rebuild
363
- * the original content even after auto-wrap inserted line breaks.
364
- */
365
- function measureSegment(text, refTextElement) {
366
- if (!text) return 0;
367
- const NS = 'http://www.w3.org/2000/svg';
368
- const probe = document.createElementNS(NS, 'tspan');
369
- probe.textContent = text;
370
- refTextElement.appendChild(probe);
371
- let width = 0;
372
- try { width = probe.getComputedTextLength(); } catch {}
373
- refTextElement.removeChild(probe);
374
- return width;
375
- }
376
-
377
- function paintTextContent(textElement, source, wrapWidth) {
324
+ /** Render only explicit editor line breaks; text never soft-wraps. */
325
+ function paintTextContent(textElement, source) {
378
326
  while (textElement.firstChild) textElement.removeChild(textElement.firstChild);
379
327
  const x = textElement.getAttribute('x') || 0;
380
328
  const NS = 'http://www.w3.org/2000/svg';
381
- const paragraphs = (source || '').split('\n');
382
- const finalLines = [];
383
-
384
- for (const paragraph of paragraphs) {
385
- const para = paragraph.replace(/ /g, ' ');
386
- if (!wrapWidth || wrapWidth <= 0) {
387
- finalLines.push(para || ' ');
388
- continue;
389
- }
390
- // Word-wrap the paragraph against wrapWidth.
391
- const words = para.split(/(\s+)/); // keep separators so spacing survives
392
- let cur = '';
393
- for (const token of words) {
394
- const candidate = cur + token;
395
- if (measureSegment(candidate, textElement) > wrapWidth && cur.trim().length > 0) {
396
- finalLines.push(cur);
397
- cur = token.replace(/^\s+/, ''); // drop the leading break-space
398
- } else {
399
- cur = candidate;
400
- }
401
- }
402
- finalLines.push(cur.length ? cur : ' ');
403
- }
329
+ const finalLines = (source || '').split('\n').map((line) => line.replace(/ /g, ' '));
404
330
 
405
331
  finalLines.forEach((line, i) => {
406
332
  const tspan = document.createElementNS(NS, 'tspan');
@@ -411,21 +337,6 @@ function paintTextContent(textElement, source, wrapWidth) {
411
337
  });
412
338
  }
413
339
 
414
- function getWrapSource(textElement) {
415
- const stored = textElement.getAttribute('data-wrap-source');
416
- if (stored !== null && stored !== '') return stored;
417
- // Fallback: rebuild from existing tspans (legacy text shapes without
418
- // the attribute). Join each tspan as a paragraph.
419
- const tspans = textElement.querySelectorAll('tspan');
420
- if (tspans.length === 0) return textElement.textContent || '';
421
- return Array.from(tspans).map((t) => (t.textContent || '').replace(/ /g, ' ')).join('\n');
422
- }
423
-
424
- function getWrapWidth(textElement) {
425
- const v = parseFloat(textElement.getAttribute('data-wrap-width') || '');
426
- return Number.isFinite(v) && v > 0 ? v : 0;
427
- }
428
-
429
340
  function renderText(input, textElement, deleteIfEmpty = false) {
430
341
  if (!input || !document.body.contains(input)) {
431
342
  return;
@@ -483,13 +394,12 @@ function renderText(input, textElement, deleteIfEmpty = false) {
483
394
  removeSelectionFeedback();
484
395
  }
485
396
  } else {
486
- // Persist the raw editor content so future E/W resizes can re-wrap
487
- // from the user's original line breaks instead of the auto-inserted
488
- // ones. Then call the shared paint helper \u2014 it falls back to the
489
- // legacy "explicit newlines only" path when no wrap-width is set.
397
+ // Persist and render only the user's explicit Shift+Enter line
398
+ // breaks. Long lines remain long on the infinite canvas.
490
399
  const cleanSource = text.replace(/\u00A0/g, ' ');
491
400
  textElement.setAttribute('data-wrap-source', cleanSource);
492
- paintTextContent(textElement, cleanSource, getWrapWidth(textElement));
401
+ textElement.removeAttribute('data-wrap-width');
402
+ paintTextContent(textElement, cleanSource);
493
403
 
494
404
  gElement.style.display = 'block';
495
405
 
@@ -551,18 +461,13 @@ function createSelectionFeedback(groupElement) {
551
461
  selectionBox.setAttribute("pointer-events", "none");
552
462
  groupElement.appendChild(selectionBox);
553
463
 
554
- // Issue #48 bug #5 + phase D follow-up: E/W midpoint anchors drive
555
- // TRUE word-wrap. Dragging E/W sets `data-wrap-width` on the text
556
- // element; the paint helper re-flows tspans so the text wraps into
557
- // multiple lines. Corner anchors keep their font-size scaling
558
- // behaviour.
464
+ // Corner anchors scale font size. Width handles are intentionally
465
+ // omitted because text width is content-driven and never soft-wraps.
559
466
  const handlesData = [
560
467
  { name: 'nw', x: selX, y: selY, cursor: 'nwse-resize' },
561
468
  { name: 'ne', x: selX + selWidth, y: selY, cursor: 'nesw-resize' },
562
469
  { name: 'sw', x: selX, y: selY + selHeight, cursor: 'nesw-resize' },
563
470
  { name: 'se', x: selX + selWidth, y: selY + selHeight, cursor: 'nwse-resize' },
564
- { name: 'e', x: selX + selWidth, y: selY + selHeight / 2, cursor: 'ew-resize' },
565
- { name: 'w', x: selX, y: selY + selHeight / 2, cursor: 'ew-resize' },
566
471
  ];
567
472
 
568
473
  resizeHandles = {};
@@ -668,10 +573,6 @@ function updateSelectionFeedback() {
668
573
  { name: 'ne', x: selX + selWidth, y: selY },
669
574
  { name: 'sw', x: selX, y: selY + selHeight },
670
575
  { name: 'se', x: selX + selWidth, y: selY + selHeight },
671
- // Issue #48 bug #5: keep E/W midpoint handles aligned with the
672
- // resize so they follow the box as it scales.
673
- { name: 'e', x: selX + selWidth, y: selY + selHeight / 2 },
674
- { name: 'w', x: selX, y: selY + selHeight / 2 },
675
576
  ];
676
577
 
677
578
  handlesData.forEach(handle => {
@@ -963,7 +864,6 @@ const handleMouseMove = (event) => {
963
864
  const startHeight = startBBox.height;
964
865
 
965
866
  let anchorX, anchorY;
966
- const isEdgeWidth = currentResizeHandle === 'e' || currentResizeHandle === 'w';
967
867
 
968
868
  switch (currentResizeHandle) {
969
869
  case 'nw':
@@ -982,34 +882,8 @@ const handleMouseMove = (event) => {
982
882
  anchorX = startX;
983
883
  anchorY = startY;
984
884
  break;
985
- case 'e':
986
- anchorX = startX;
987
- anchorY = startY + startHeight / 2;
988
- break;
989
- case 'w':
990
- anchorX = startX + startWidth;
991
- anchorY = startY + startHeight / 2;
992
- break;
993
- }
994
-
995
- // Issue #48 phase D follow-up: E/W now drives TRUE word-wrap.
996
- // The drag distance becomes the wrap-width target; we re-paint
997
- // the text with the new width and short-circuit the font-size
998
- // scaling path that the corners use.
999
- if (isEdgeWidth) {
1000
- const newWrap = Math.abs(currentPoint.x - anchorX);
1001
- const minWrap = startFontSize * 2; // at least ~one short word wide
1002
- const clampedWrap = Math.max(minWrap, newWrap);
1003
- textElement.setAttribute('data-wrap-width', String(clampedWrap));
1004
- const source = getWrapSource(textElement);
1005
- paintTextContent(textElement, source, clampedWrap);
1006
- if (typeof updateSelectionFeedback === 'function') {
1007
- setTimeout(updateSelectionFeedback, 0);
1008
- }
1009
- return;
1010
885
  }
1011
886
 
1012
- const newWidth = Math.abs(currentPoint.x - anchorX);
1013
887
  const newHeight = Math.abs(currentPoint.y - anchorY);
1014
888
  const chosenScale = newHeight / startHeight;
1015
889
 
@@ -1044,14 +918,6 @@ const handleMouseMove = (event) => {
1044
918
  newAnchorX = currentBBox.x;
1045
919
  newAnchorY = currentBBox.y;
1046
920
  break;
1047
- case 'e':
1048
- newAnchorX = currentBBox.x;
1049
- newAnchorY = currentBBox.y + currentBBox.height / 2;
1050
- break;
1051
- case 'w':
1052
- newAnchorX = currentBBox.x + currentBBox.width;
1053
- newAnchorY = currentBBox.y + currentBBox.height / 2;
1054
- break;
1055
921
  }
1056
922
 
1057
923
  const deltaX = anchorX - newAnchorX;
@@ -1,8 +0,0 @@
1
- "use client";
2
- import {
3
- TextShape
4
- } from "./chunk-QXFBG7OM.js";
5
- export {
6
- TextShape
7
- };
8
- //# sourceMappingURL=TextShape-54ZJ66E3.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/shapes/TextShape.js"],
4
- "sourcesContent": ["/* eslint-disable */\n// TextShape class - extracted from writeText.js\n// Depends on globals: svg, shapes, currentShape\n\nfunction extractRotationFromTransform(el) {\n const t = el.getAttribute(\"transform\") || \"\";\n const m = t.match(/rotate\\(([^,)]+)/);\n return m ? parseFloat(m[1]) : 0;\n}\nfunction updateAttachedArrows(wrapper) {\n if (!wrapper || typeof shapes === \"undefined\") return;\n shapes.forEach(s => {\n if (s && s.shapeName === \"arrow\" && typeof s.updateAttachments === \"function\") {\n if ((s.attachedToStart && s.attachedToStart.shape === wrapper) ||\n (s.attachedToEnd && s.attachedToEnd.shape === wrapper)) {\n s.updateAttachments();\n }\n }\n });\n}\nlet isDragging = false;\nlet hoveredFrameText = null;\nlet selectedElement = null;\nfunction updateSelectionFeedback() {}\nfunction deselectElement() { selectedElement = null; }\nfunction selectElement(el) { selectedElement = el; }\n\nclass TextShape {\n constructor(groupElement) {\n this.group = groupElement;\n this.shapeName = 'text';\n this.shapeID = groupElement.getAttribute('id') || `text-${String(Date.now()).slice(0, 8)}-${Math.floor(Math.random() * 10000)}`;\n \n // Frame attachment properties\n this.parentFrame = null;\n \n // Update group attributes \u2014 ensure data-type is set for textTool interaction\n this.group.setAttribute('type', 'text');\n this.group.setAttribute('data-type', 'text-group');\n this.group.shapeName = 'text';\n this.group.shapeID = this.shapeID;\n }\n \n // Position and dimension properties for frame compatibility\n get x() {\n const transform = this.group.transform.baseVal.consolidate();\n return transform ? transform.matrix.e : parseFloat(this.group.getAttribute('data-x')) || 0;\n }\n \n set x(value) {\n const transform = this.group.transform.baseVal.consolidate();\n const currentY = transform ? transform.matrix.f : parseFloat(this.group.getAttribute('data-y')) || 0;\n const rotation = extractRotationFromTransform(this.group) || 0;\n const textElement = this.group.querySelector('text');\n if (textElement) {\n const bbox = textElement.getBBox();\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n this.group.setAttribute('transform', `translate(${value}, ${currentY}) rotate(${rotation}, ${centerX}, ${centerY})`);\n } else {\n this.group.setAttribute('transform', `translate(${value}, ${currentY})`);\n }\n this.group.setAttribute('data-x', value);\n }\n \n get y() {\n const transform = this.group.transform.baseVal.consolidate();\n return transform ? transform.matrix.f : parseFloat(this.group.getAttribute('data-y')) || 0;\n }\n \n set y(value) {\n const transform = this.group.transform.baseVal.consolidate();\n const currentX = transform ? transform.matrix.e : parseFloat(this.group.getAttribute('data-x')) || 0;\n const rotation = extractRotationFromTransform(this.group) || 0;\n const textElement = this.group.querySelector('text');\n if (textElement) {\n const bbox = textElement.getBBox();\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n this.group.setAttribute('transform', `translate(${currentX}, ${value}) rotate(${rotation}, ${centerX}, ${centerY})`);\n } else {\n this.group.setAttribute('transform', `translate(${currentX}, ${value})`);\n }\n this.group.setAttribute('data-y', value);\n }\n \n get width() {\n const textElement = this.group.querySelector('text');\n if (textElement) {\n return textElement.getBBox().width;\n }\n return 0;\n }\n \n set width(value) {\n // Text width is determined by content and font size, not directly settable\n // This is here for frame compatibility but doesn't change the text\n }\n \n get height() {\n const textElement = this.group.querySelector('text');\n if (textElement) {\n return textElement.getBBox().height;\n }\n return 0;\n }\n \n set height(value) {\n // Text height is determined by content and font size, not directly settable\n // This is here for frame compatibility but doesn't change the text\n }\n \n get rotation() {\n return extractRotationFromTransform(this.group) || 0;\n }\n \n set rotation(value) {\n const currentTransform = this.group.transform.baseVal.consolidate();\n const currentX = currentTransform ? currentTransform.matrix.e : 0;\n const currentY = currentTransform ? currentTransform.matrix.f : 0;\n const textElement = this.group.querySelector('text');\n if (textElement) {\n const bbox = textElement.getBBox();\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n this.group.setAttribute('transform', `translate(${currentX}, ${currentY}) rotate(${value}, ${centerX}, ${centerY})`);\n }\n }\n\n move(dx, dy) {\n const currentTransform = this.group.transform.baseVal.consolidate();\n const currentX = currentTransform ? currentTransform.matrix.e : 0;\n const currentY = currentTransform ? currentTransform.matrix.f : 0;\n\n this.x = currentX + dx;\n this.y = currentY + dy;\n\n // Only update frame containment if we're actively dragging the shape itself\n // and not being moved by a parent frame\n if (isDragging && !this.isBeingMovedByFrame) {\n this.updateFrameContainment();\n }\n\n this.updateAttachedArrows();\n }\n\n updateAttachedArrows() {\n updateAttachedArrows(this);\n }\n\n updateFrameContainment() {\n // Don't update if we're being moved by a frame\n if (this.isBeingMovedByFrame) return;\n \n let targetFrame = null;\n \n // Find which frame this shape is over\n if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {\n shapes.forEach(shape => {\n if (shape.shapeName === 'frame' && shape.isShapeInFrame(this)) {\n targetFrame = shape;\n }\n });\n }\n \n // If we have a parent frame and we're being dragged, temporarily remove clipping\n if (this.parentFrame && isDragging) {\n this.parentFrame.temporarilyRemoveFromFrame(this);\n }\n \n // Update frame highlighting\n if (hoveredFrameText && hoveredFrameText !== targetFrame) {\n hoveredFrameText.removeHighlight();\n }\n \n if (targetFrame && targetFrame !== hoveredFrameText) {\n targetFrame.highlightFrame();\n }\n \n hoveredFrameText = targetFrame;\n }\n\n contains(x, y) {\n const textElement = this.group.querySelector('text');\n if (!textElement || typeof textElement.getBBox !== 'function') return false;\n\n let bbox;\n try { bbox = textElement.getBBox(); } catch { return false; }\n const padding = 8; // Selection padding\n\n const CTM = this.group.getCTM();\n if (!CTM) return false;\n \n const inverseCTM = CTM.inverse();\n const svgPoint = svg.createSVGPoint();\n svgPoint.x = x;\n svgPoint.y = y;\n const transformedPoint = svgPoint.matrixTransform(inverseCTM);\n \n return transformedPoint.x >= bbox.x - padding && \n transformedPoint.x <= bbox.x + bbox.width + padding &&\n transformedPoint.y >= bbox.y - padding && \n transformedPoint.y <= bbox.y + bbox.height + padding;\n }\n\n // Add draw method for consistency with other shapes\n draw() {\n // Text doesn't need redrawing like other shapes, but we need this method for consistency\n if (selectedElement === this.group) {\n updateSelectionFeedback();\n }\n }\n\n // Add methods for frame compatibility\n removeSelection() {\n if (selectedElement === this.group) {\n deselectElement();\n }\n }\n\n selectShape() {\n // Use the real textTool selectElement (with selection feedback) if available\n if (typeof window !== 'undefined' && window.__selectTextElement) {\n window.__selectTextElement(this.group);\n } else {\n selectElement(this.group);\n }\n }\n}\n\nexport { TextShape };\n"],
5
- "mappings": "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",
6
- "names": []
7
- }