@elixpo/lixsketch 5.4.1 → 5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elixpo/lixsketch",
3
- "version": "5.4.1",
3
+ "version": "5.4.2",
4
4
  "description": "Open-source SVG whiteboard engine with hand-drawn aesthetics — the core of LixSketch",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -8,7 +8,7 @@ import { handleMouseDownCircle, handleMouseMoveCircle, handleMouseUpCircle } fro
8
8
  import { handleMouseUpImage, handleMouseDownImage, handleMouseMoveImage } from '../tools/imageTool.js';
9
9
  import { handleMouseDownLine, handleMouseMoveLine, handleMouseUpLine } from '../tools/lineTool.js';
10
10
  import { handleFreehandMouseDown, handleFreehandMouseMove, handleFreehandMouseUp } from '../tools/freehandTool.js';
11
- import { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp } from '../tools/textTool.js';
11
+ import { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, enterEditMode } from '../tools/textTool.js';
12
12
  import { handleMouseDownFrame, handleMouseMoveFrame, handleMouseUpFrame } from '../tools/frameTool.js';
13
13
  import { handleMultiSelectionMouseDown, handleMultiSelectionMouseMove, handleMultiSelectionMouseUp, removeMultiSelectionRect, multiSelection, isMultiSelecting} from './Selection.js';
14
14
  import { handleMouseDownIcon, handleMouseMoveIcon, handleMouseUpIcon } from '../tools/iconTool.js';
@@ -464,6 +464,15 @@ function _onMouseEnter(e) {
464
464
  }
465
465
  }
466
466
 
467
+ const handleMainDblClick = (e) => {
468
+ // Double-click on a text group from any tool: enter text edit mode
469
+ const targetTextGroup = e.target.closest('g[data-type="text-group"]');
470
+ if (targetTextGroup) {
471
+ e.stopPropagation();
472
+ enterEditMode(targetTextGroup);
473
+ }
474
+ };
475
+
467
476
  function initEventDispatcher(svgEl) {
468
477
  if (_boundSvg) cleanupEventDispatcher();
469
478
  const target = svgEl || svg;
@@ -472,6 +481,7 @@ function initEventDispatcher(svgEl) {
472
481
  target.addEventListener('mouseup', handleMainMouseUp);
473
482
  target.addEventListener('mouseleave', handleMainMouseLeave);
474
483
  target.addEventListener('mouseenter', _onMouseEnter);
484
+ target.addEventListener('dblclick', handleMainDblClick);
475
485
  _boundSvg = target;
476
486
  }
477
487
 
@@ -488,6 +498,7 @@ function cleanupEventDispatcher() {
488
498
  _boundSvg.removeEventListener('mouseup', handleMainMouseUp);
489
499
  _boundSvg.removeEventListener('mouseleave', handleMainMouseLeave);
490
500
  _boundSvg.removeEventListener('mouseenter', _onMouseEnter);
501
+ _boundSvg.removeEventListener('dblclick', handleMainDblClick);
491
502
  _boundSvg = null;
492
503
  }
493
504
  }
@@ -80,9 +80,10 @@ function highlightShapesInSelectionRect(currentX, currentY) {
80
80
  shapes.forEach(shape => {
81
81
  if (isShapeInSelectionRect(shape, selBounds)) {
82
82
  // Add a semi-transparent overlay to the shape's group
83
- if (shape.group) {
83
+ if (shape.group && typeof shape.group.getBBox === 'function') {
84
+ let bbox;
85
+ try { bbox = shape.group.getBBox(); } catch { return; }
84
86
  const overlay = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
85
- const bbox = shape.group.getBBox();
86
87
  overlay.setAttribute('x', bbox.x - 2);
87
88
  overlay.setAttribute('y', bbox.y - 2);
88
89
  overlay.setAttribute('width', bbox.width + 4);
@@ -202,11 +202,12 @@ class CodeShape {
202
202
 
203
203
  contains(x, y) {
204
204
  const codeElement = this.group.querySelector('text');
205
- if (!codeElement) return false;
206
-
207
- const bbox = codeElement.getBBox();
205
+ if (!codeElement || typeof codeElement.getBBox !== 'function') return false;
206
+
207
+ let bbox;
208
+ try { bbox = codeElement.getBBox(); } catch { return false; }
208
209
  const padding = 8; // Selection padding
209
-
210
+
210
211
  const CTM = this.group.getCTM();
211
212
  if (!CTM) return false;
212
213
 
@@ -182,11 +182,12 @@ class TextShape {
182
182
 
183
183
  contains(x, y) {
184
184
  const textElement = this.group.querySelector('text');
185
- if (!textElement) return false;
186
-
187
- const bbox = textElement.getBBox();
185
+ if (!textElement || typeof textElement.getBBox !== 'function') return false;
186
+
187
+ let bbox;
188
+ try { bbox = textElement.getBBox(); } catch { return false; }
188
189
  const padding = 8; // Selection padding
189
-
190
+
190
191
  const CTM = this.group.getCTM();
191
192
  if (!CTM) return false;
192
193
 
@@ -51,6 +51,7 @@ let rotationStartTransform = null;
51
51
  let initialHandlePosRelGroup = null;
52
52
  let initialGroupTx = 0;
53
53
  let initialGroupTy = 0;
54
+ let initialInverseScreenCTM = null;
54
55
 
55
56
  // Frame attachment variables
56
57
  let draggedShapeInitialFrameText = null;
@@ -753,6 +754,10 @@ function startResize(event, anchor) {
753
754
 
754
755
  startPoint = getSVGCoordinates(event, selectedElement);
755
756
 
757
+ // Freeze the group's screen CTM at resize start so mouse→local mapping stays stable
758
+ const groupScreenCTM = selectedElement.getScreenCTM();
759
+ initialInverseScreenCTM = groupScreenCTM ? groupScreenCTM.inverse() : null;
760
+
756
761
  const currentTransform = selectedElement.transform.baseVal.consolidate();
757
762
  initialGroupTx = currentTransform ? currentTransform.matrix.e : 0;
758
763
  initialGroupTy = currentTransform ? currentTransform.matrix.f : 0;
@@ -828,7 +833,16 @@ const handleMouseMove = (event) => {
828
833
  const textElement = selectedElement.querySelector('text');
829
834
  if (!textElement || !startBBox || startFontSize === null || !startPoint || !initialHandlePosRelGroup) return;
830
835
 
831
- const currentPoint = getSVGCoordinates(event, selectedElement);
836
+ // Use the frozen initial CTM so the mapping doesn't shift as we change the group transform
837
+ let currentPoint;
838
+ if (initialInverseScreenCTM) {
839
+ const pt = svg.createSVGPoint();
840
+ pt.x = event.clientX;
841
+ pt.y = event.clientY;
842
+ currentPoint = pt.matrixTransform(initialInverseScreenCTM);
843
+ } else {
844
+ currentPoint = getSVGCoordinates(event, selectedElement);
845
+ }
832
846
 
833
847
  const startX = startBBox.x;
834
848
  const startY = startBBox.y;
@@ -1820,4 +1834,4 @@ window.__setCodeLanguage = function(lang) {
1820
1834
  }
1821
1835
  };
1822
1836
 
1823
- export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement };
1837
+ export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement, enterEditMode };