@elixpo/lixsketch 5.4.2 → 5.4.4
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 +3 -3
- package/src/SketchEngine.js +7 -0
- package/src/core/CopyPaste.js +1 -1
- package/src/core/EventDispatcher.js +27 -19
- package/src/core/SceneSerializer.js +12 -4
- package/src/core/Selection.js +54 -24
- package/src/core/ZoomPan.js +15 -9
- package/src/shapes/Arrow.js +3 -3
- package/src/shapes/Circle.js +3 -3
- package/src/shapes/Frame.js +5 -5
- package/src/shapes/Line.js +3 -3
- package/src/shapes/Rectangle.js +3 -3
- package/src/shapes/TextShape.js +6 -1
- package/src/tools/arrowTool.js +3 -3
- package/src/tools/codeTool.js +17 -16
- package/src/tools/freehandTool.js +4 -4
- package/src/tools/iconTool.js +112 -99
- package/src/tools/imageTool.js +25 -25
- package/src/tools/textTool.js +24 -21
package/src/tools/iconTool.js
CHANGED
|
@@ -41,18 +41,16 @@ function removeSelection() {
|
|
|
41
41
|
const svg = getSVGElement();
|
|
42
42
|
if (!svg) return;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
svg.removeChild(outline);
|
|
47
|
-
}
|
|
44
|
+
// Remove ALL selection outlines (prevents orphaned ghost elements)
|
|
45
|
+
svg.querySelectorAll(".selection-outline").forEach(el => el.remove());
|
|
48
46
|
|
|
49
47
|
removeResizeAnchors();
|
|
50
48
|
removeRotationAnchor();
|
|
51
49
|
|
|
52
50
|
if (selectedIcon) {
|
|
53
|
-
selectedIcon.removeEventListener('
|
|
54
|
-
selectedIcon.removeEventListener('
|
|
55
|
-
selectedIcon.removeEventListener('
|
|
51
|
+
selectedIcon.removeEventListener('pointerdown', startDrag);
|
|
52
|
+
selectedIcon.removeEventListener('pointerup', stopDrag);
|
|
53
|
+
selectedIcon.removeEventListener('pointerleave', stopDrag);
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
|
|
@@ -190,12 +188,13 @@ const drawMiniatureIcon = () => {
|
|
|
190
188
|
};
|
|
191
189
|
|
|
192
190
|
const handleMouseDownIcon = async (e) => {
|
|
191
|
+
if (!e.target) return;
|
|
193
192
|
if (isSelectionToolActive) {
|
|
194
193
|
const clickedIcon = e.target.closest('[type="icon"]');
|
|
195
194
|
if (clickedIcon) {
|
|
196
195
|
e.preventDefault();
|
|
197
196
|
e.stopPropagation();
|
|
198
|
-
|
|
197
|
+
|
|
199
198
|
if (selectedIcon === clickedIcon) {
|
|
200
199
|
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
201
200
|
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
@@ -221,24 +220,24 @@ const handleMouseDownIcon = async (e) => {
|
|
|
221
220
|
const deltaY = Math.abs(currentY - initialMouseY);
|
|
222
221
|
|
|
223
222
|
if (deltaX > dragThreshold || deltaY > dragThreshold) {
|
|
224
|
-
document.removeEventListener('
|
|
225
|
-
document.removeEventListener('
|
|
226
|
-
window.removeEventListener('
|
|
223
|
+
document.removeEventListener('pointermove', checkDragStartWithThreshold);
|
|
224
|
+
document.removeEventListener('pointerup', cancelDragPrep);
|
|
225
|
+
window.removeEventListener('pointerup', cancelDragPrep);
|
|
227
226
|
|
|
228
227
|
const svg = getSVGElement();
|
|
229
|
-
if (svg) svg.removeEventListener('
|
|
228
|
+
if (svg) svg.removeEventListener('pointerup', cancelDragPrep);
|
|
230
229
|
|
|
231
230
|
startDrag(moveEvent);
|
|
232
231
|
}
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
_pendingDragChecker = checkDragStartWithThreshold;
|
|
236
|
-
document.addEventListener('
|
|
237
|
-
document.addEventListener('
|
|
238
|
-
window.addEventListener('
|
|
235
|
+
document.addEventListener('pointermove', checkDragStartWithThreshold);
|
|
236
|
+
document.addEventListener('pointerup', cancelDragPrep);
|
|
237
|
+
window.addEventListener('pointerup', cancelDragPrep);
|
|
239
238
|
|
|
240
239
|
const svg = getSVGElement();
|
|
241
|
-
if (svg) svg.addEventListener('
|
|
240
|
+
if (svg) svg.addEventListener('pointerup', cancelDragPrep);
|
|
242
241
|
|
|
243
242
|
return;
|
|
244
243
|
} else {
|
|
@@ -246,6 +245,16 @@ const handleMouseDownIcon = async (e) => {
|
|
|
246
245
|
return;
|
|
247
246
|
}
|
|
248
247
|
}
|
|
248
|
+
|
|
249
|
+
// Clicked on empty canvas while an icon is selected — deselect it
|
|
250
|
+
if (selectedIcon) {
|
|
251
|
+
removeSelection();
|
|
252
|
+
selectedIcon = null;
|
|
253
|
+
if (currentShape && currentShape.shapeName === 'icon') {
|
|
254
|
+
currentShape = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
if (!isDraggingIcon || !iconToPlace || !isIconToolActive) {
|
|
@@ -358,10 +367,11 @@ const handleMouseDownIcon = async (e) => {
|
|
|
358
367
|
placedIconShape = iconShape;
|
|
359
368
|
|
|
360
369
|
if (typeof shapes !== 'undefined' && Array.isArray(shapes)) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
370
|
+
// Prevent duplicates — check if this element is already tracked
|
|
371
|
+
const alreadyExists = shapes.some(s => s.shapeName === 'icon' && s.element === finalIconGroup);
|
|
372
|
+
if (!alreadyExists) {
|
|
373
|
+
shapes.push(iconShape);
|
|
374
|
+
}
|
|
365
375
|
}
|
|
366
376
|
|
|
367
377
|
const finalFrame = hoveredFrameIcon;
|
|
@@ -403,18 +413,10 @@ const handleMouseDownIcon = async (e) => {
|
|
|
403
413
|
};
|
|
404
414
|
|
|
405
415
|
const handleMouseUpIcon = (e) => {
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
clickedElement.classList.contains('rotation-anchor') ||
|
|
411
|
-
clickedElement.classList.contains('selection-outline');
|
|
412
|
-
|
|
413
|
-
if (!isIconElement && !isAnchorElement && selectedIcon) {
|
|
414
|
-
removeSelection();
|
|
415
|
-
selectedIcon = null;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
416
|
+
if (!e.target) return;
|
|
417
|
+
// Deselection on empty-canvas click is handled by handleMouseDownIcon.
|
|
418
|
+
// We intentionally do NOT deselect on mouseup — after a drag the cursor
|
|
419
|
+
// often ends up on the background, and that shouldn't kill the selection.
|
|
418
420
|
|
|
419
421
|
if (hoveredFrameIcon) {
|
|
420
422
|
hoveredFrameIcon.removeHighlight();
|
|
@@ -468,21 +470,21 @@ function addSelectionOutline() {
|
|
|
468
470
|
}
|
|
469
471
|
|
|
470
472
|
function checkDragStart(event) {
|
|
471
|
-
document.removeEventListener('
|
|
472
|
-
document.removeEventListener('
|
|
473
|
+
document.removeEventListener('pointermove', checkDragStart);
|
|
474
|
+
document.removeEventListener('pointerup', cancelDragPrep);
|
|
473
475
|
startDrag(event);
|
|
474
476
|
}
|
|
475
477
|
function cancelDragPrep(event) {
|
|
476
478
|
if (_pendingDragChecker) {
|
|
477
|
-
document.removeEventListener('
|
|
479
|
+
document.removeEventListener('pointermove', _pendingDragChecker);
|
|
478
480
|
_pendingDragChecker = null;
|
|
479
481
|
}
|
|
480
|
-
document.removeEventListener('
|
|
481
|
-
window.removeEventListener('
|
|
482
|
+
document.removeEventListener('pointerup', cancelDragPrep);
|
|
483
|
+
window.removeEventListener('pointerup', cancelDragPrep);
|
|
482
484
|
|
|
483
485
|
const svg = getSVGElement();
|
|
484
486
|
if (svg) {
|
|
485
|
-
svg.removeEventListener('
|
|
487
|
+
svg.removeEventListener('pointerup', cancelDragPrep);
|
|
486
488
|
}
|
|
487
489
|
}
|
|
488
490
|
|
|
@@ -506,14 +508,14 @@ function startDrag(event) {
|
|
|
506
508
|
}
|
|
507
509
|
}
|
|
508
510
|
|
|
509
|
-
document.addEventListener('
|
|
510
|
-
document.addEventListener('
|
|
511
|
+
document.addEventListener('pointermove', dragIcon);
|
|
512
|
+
document.addEventListener('pointerup', stopDrag);
|
|
511
513
|
|
|
512
|
-
window.addEventListener('
|
|
514
|
+
window.addEventListener('pointerup', stopDrag);
|
|
513
515
|
|
|
514
516
|
const svg = getSVGElement();
|
|
515
517
|
if (svg) {
|
|
516
|
-
svg.addEventListener('
|
|
518
|
+
svg.addEventListener('pointerup', stopDrag);
|
|
517
519
|
}
|
|
518
520
|
|
|
519
521
|
document.addEventListener('dragstart', preventDefaultDrag);
|
|
@@ -530,7 +532,7 @@ function selectIcon(event) {
|
|
|
530
532
|
|
|
531
533
|
event.stopPropagation();
|
|
532
534
|
|
|
533
|
-
let targetIcon = event.target.closest('[type="icon"]');
|
|
535
|
+
let targetIcon = event.target.closest ? event.target.closest('[type="icon"]') : null;
|
|
534
536
|
if (!targetIcon) {
|
|
535
537
|
let current = event.target;
|
|
536
538
|
while (current && current !== document) {
|
|
@@ -542,44 +544,43 @@ function selectIcon(event) {
|
|
|
542
544
|
}
|
|
543
545
|
}
|
|
544
546
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
547
|
+
// Always clean up previous selection before applying new one
|
|
548
|
+
if (selectedIcon) {
|
|
549
|
+
removeSelection();
|
|
550
|
+
}
|
|
549
551
|
|
|
550
|
-
|
|
552
|
+
selectedIcon = targetIcon;
|
|
551
553
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
if (!selectedIcon) {
|
|
555
|
+
console.warn('Could not find icon to select');
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
556
558
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
563
|
-
} else {
|
|
564
|
-
iconRotation = 0;
|
|
559
|
+
const transform = selectedIcon.getAttribute('transform');
|
|
560
|
+
if (transform) {
|
|
561
|
+
const rotateMatch = transform.match(/rotate\(([^,\s]+)/);
|
|
562
|
+
if (rotateMatch) {
|
|
563
|
+
iconRotation = parseFloat(rotateMatch[1]);
|
|
565
564
|
}
|
|
565
|
+
} else {
|
|
566
|
+
iconRotation = 0;
|
|
567
|
+
}
|
|
566
568
|
|
|
567
|
-
|
|
569
|
+
addSelectionOutline();
|
|
568
570
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
571
|
+
originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
|
|
572
|
+
originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
|
|
573
|
+
originalWidth = parseFloat(selectedIcon.getAttribute('width')) || placedIconSize;
|
|
574
|
+
originalHeight = parseFloat(selectedIcon.getAttribute('height')) || placedIconSize;
|
|
573
575
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
576
|
+
// Set currentShape so EventDispatcher routes subsequent events to icon handler
|
|
577
|
+
const iconShape = (typeof shapes !== 'undefined' && Array.isArray(shapes))
|
|
578
|
+
? shapes.find(s => s.shapeName === 'icon' && s.element === selectedIcon)
|
|
579
|
+
: null;
|
|
580
|
+
if (iconShape) {
|
|
581
|
+
currentShape = iconShape;
|
|
582
|
+
currentShape.isSelected = true;
|
|
583
|
+
if (window.__showSidebarForShape) window.__showSidebarForShape('icon');
|
|
583
584
|
}
|
|
584
585
|
}
|
|
585
586
|
|
|
@@ -613,8 +614,8 @@ function addResizeAnchors(x, y, width, height, centerX, centerY, iconWidth, rota
|
|
|
613
614
|
|
|
614
615
|
svg.appendChild(anchor);
|
|
615
616
|
|
|
616
|
-
anchor.addEventListener('
|
|
617
|
-
anchor.addEventListener('
|
|
617
|
+
anchor.addEventListener('pointerdown', startResize);
|
|
618
|
+
anchor.addEventListener('pointerup', stopResize);
|
|
618
619
|
});
|
|
619
620
|
}
|
|
620
621
|
|
|
@@ -642,8 +643,8 @@ function addRotationAnchor(x, y, width, height, centerX, centerY, iconWidth, rot
|
|
|
642
643
|
|
|
643
644
|
svg.appendChild(rotationAnchor);
|
|
644
645
|
|
|
645
|
-
rotationAnchor.addEventListener('
|
|
646
|
-
rotationAnchor.addEventListener('
|
|
646
|
+
rotationAnchor.addEventListener('pointerdown', startRotation);
|
|
647
|
+
rotationAnchor.addEventListener('pointerup', stopRotation);
|
|
647
648
|
|
|
648
649
|
rotationAnchor.addEventListener('mouseover', function() {
|
|
649
650
|
if (!isRotatingIcon && !isDragging) {
|
|
@@ -662,10 +663,7 @@ function removeRotationAnchor() {
|
|
|
662
663
|
const svg = getSVGElement();
|
|
663
664
|
if (!svg) return;
|
|
664
665
|
|
|
665
|
-
|
|
666
|
-
if (rotationAnchor) {
|
|
667
|
-
svg.removeChild(rotationAnchor);
|
|
668
|
-
}
|
|
666
|
+
svg.querySelectorAll(".rotation-anchor").forEach(el => el.remove());
|
|
669
667
|
}
|
|
670
668
|
|
|
671
669
|
function removeResizeAnchors() {
|
|
@@ -695,14 +693,14 @@ function startResize(event) {
|
|
|
695
693
|
|
|
696
694
|
const svg = getSVGElement();
|
|
697
695
|
if (svg) {
|
|
698
|
-
svg.addEventListener('
|
|
696
|
+
svg.addEventListener('pointermove', resizeIcon);
|
|
699
697
|
}
|
|
700
|
-
document.addEventListener('
|
|
698
|
+
document.addEventListener('pointerup', stopResize);
|
|
701
699
|
}
|
|
702
700
|
|
|
703
701
|
function stopResize(event) {
|
|
704
702
|
stopInteracting();
|
|
705
|
-
document.removeEventListener('
|
|
703
|
+
document.removeEventListener('pointerup', stopResize);
|
|
706
704
|
}
|
|
707
705
|
|
|
708
706
|
function resizeIcon(event) {
|
|
@@ -875,13 +873,13 @@ function stopDrag(event) {
|
|
|
875
873
|
isDragging = false;
|
|
876
874
|
if (window.__iconShapeState) window.__iconShapeState.isDragging = false;
|
|
877
875
|
|
|
878
|
-
document.removeEventListener('
|
|
879
|
-
document.removeEventListener('
|
|
880
|
-
window.removeEventListener('
|
|
876
|
+
document.removeEventListener('pointermove', dragIcon);
|
|
877
|
+
document.removeEventListener('pointerup', stopDrag);
|
|
878
|
+
window.removeEventListener('pointerup', stopDrag);
|
|
881
879
|
|
|
882
880
|
const svg = getSVGElement();
|
|
883
881
|
if (svg) {
|
|
884
|
-
svg.removeEventListener('
|
|
882
|
+
svg.removeEventListener('pointerup', stopDrag);
|
|
885
883
|
}
|
|
886
884
|
|
|
887
885
|
document.removeEventListener('dragstart', preventDefaultDrag);
|
|
@@ -914,10 +912,10 @@ function startRotation(event) {
|
|
|
914
912
|
|
|
915
913
|
const svg = getSVGElement();
|
|
916
914
|
if (svg) {
|
|
917
|
-
svg.addEventListener('
|
|
915
|
+
svg.addEventListener('pointermove', rotateIcon);
|
|
918
916
|
svg.style.cursor = 'grabbing';
|
|
919
917
|
}
|
|
920
|
-
document.addEventListener('
|
|
918
|
+
document.addEventListener('pointerup', stopRotation);
|
|
921
919
|
}
|
|
922
920
|
|
|
923
921
|
function rotateIcon(event) {
|
|
@@ -965,10 +963,10 @@ function stopRotation(event) {
|
|
|
965
963
|
|
|
966
964
|
const svg = getSVGElement();
|
|
967
965
|
if (svg) {
|
|
968
|
-
svg.removeEventListener('
|
|
966
|
+
svg.removeEventListener('pointermove', rotateIcon);
|
|
969
967
|
svg.style.cursor = 'default';
|
|
970
968
|
}
|
|
971
|
-
document.removeEventListener('
|
|
969
|
+
document.removeEventListener('pointerup', stopRotation);
|
|
972
970
|
}
|
|
973
971
|
|
|
974
972
|
function stopInteracting() {
|
|
@@ -1060,14 +1058,14 @@ function stopInteracting() {
|
|
|
1060
1058
|
|
|
1061
1059
|
const svg = getSVGElement();
|
|
1062
1060
|
if (svg) {
|
|
1063
|
-
svg.removeEventListener('
|
|
1064
|
-
svg.removeEventListener('
|
|
1065
|
-
svg.removeEventListener('
|
|
1061
|
+
svg.removeEventListener('pointermove', dragIcon);
|
|
1062
|
+
svg.removeEventListener('pointermove', resizeIcon);
|
|
1063
|
+
svg.removeEventListener('pointermove', rotateIcon);
|
|
1066
1064
|
}
|
|
1067
1065
|
|
|
1068
|
-
document.removeEventListener('
|
|
1069
|
-
document.removeEventListener('
|
|
1070
|
-
document.removeEventListener('
|
|
1066
|
+
document.removeEventListener('pointermove', dragIcon);
|
|
1067
|
+
document.removeEventListener('pointermove', resizeIcon);
|
|
1068
|
+
document.removeEventListener('pointermove', rotateIcon);
|
|
1071
1069
|
|
|
1072
1070
|
currentAnchor = null;
|
|
1073
1071
|
startRotationMouseAngle = null;
|
|
@@ -1327,6 +1325,21 @@ function handleIconClick(event, filename) {
|
|
|
1327
1325
|
|
|
1328
1326
|
|
|
1329
1327
|
|
|
1328
|
+
// Clean up any lingering miniature icon or drag state (called on tool switch)
|
|
1329
|
+
function cleanupIconTool() {
|
|
1330
|
+
if (currentIconElement) {
|
|
1331
|
+
const svg = getSVGElement();
|
|
1332
|
+
if (svg && currentIconElement.parentNode === svg) {
|
|
1333
|
+
svg.removeChild(currentIconElement);
|
|
1334
|
+
}
|
|
1335
|
+
currentIconElement = null;
|
|
1336
|
+
}
|
|
1337
|
+
isDraggingIcon = false;
|
|
1338
|
+
iconToPlace = null;
|
|
1339
|
+
document.body.style.cursor = 'default';
|
|
1340
|
+
}
|
|
1341
|
+
window.__cleanupIconTool = cleanupIconTool;
|
|
1342
|
+
|
|
1330
1343
|
// Bridge for React sidebar to trigger icon placement
|
|
1331
1344
|
window.prepareIconPlacement = function(svgContent) {
|
|
1332
1345
|
iconToPlace = svgContent;
|
package/src/tools/imageTool.js
CHANGED
|
@@ -537,9 +537,9 @@ function selectImage(event) {
|
|
|
537
537
|
originalHeight = parseFloat(selectedImage.getAttribute('height'));
|
|
538
538
|
|
|
539
539
|
// Add drag event listeners to the selected image
|
|
540
|
-
selectedImage.addEventListener('
|
|
541
|
-
selectedImage.addEventListener('
|
|
542
|
-
selectedImage.addEventListener('
|
|
540
|
+
selectedImage.addEventListener('pointerdown', startDrag);
|
|
541
|
+
selectedImage.addEventListener('pointerup', stopDrag);
|
|
542
|
+
selectedImage.addEventListener('pointerleave', stopDrag);
|
|
543
543
|
|
|
544
544
|
// Set currentShape for sidebar + layer controls
|
|
545
545
|
const imageShape = (typeof shapes !== 'undefined' && Array.isArray(shapes))
|
|
@@ -611,9 +611,9 @@ function removeSelectionOutline() {
|
|
|
611
611
|
|
|
612
612
|
// Remove drag event listeners
|
|
613
613
|
if (selectedImage) {
|
|
614
|
-
selectedImage.removeEventListener('
|
|
615
|
-
selectedImage.removeEventListener('
|
|
616
|
-
selectedImage.removeEventListener('
|
|
614
|
+
selectedImage.removeEventListener('pointerdown', startDrag);
|
|
615
|
+
selectedImage.removeEventListener('pointerup', stopDrag);
|
|
616
|
+
selectedImage.removeEventListener('pointerleave', stopDrag);
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
619
|
|
|
@@ -647,8 +647,8 @@ function addResizeAnchors(x, y, width, height, centerX, centerY) {
|
|
|
647
647
|
svg.appendChild(anchor);
|
|
648
648
|
|
|
649
649
|
// Add event listeners for resizing
|
|
650
|
-
anchor.addEventListener('
|
|
651
|
-
anchor.addEventListener('
|
|
650
|
+
anchor.addEventListener('pointerdown', startResize);
|
|
651
|
+
anchor.addEventListener('pointerup', stopResize);
|
|
652
652
|
});
|
|
653
653
|
}
|
|
654
654
|
|
|
@@ -672,8 +672,8 @@ function addRotationAnchor(x, y, width, height, centerX, centerY) {
|
|
|
672
672
|
svg.appendChild(rotationAnchor);
|
|
673
673
|
|
|
674
674
|
// Add event listeners for rotation
|
|
675
|
-
rotationAnchor.addEventListener('
|
|
676
|
-
rotationAnchor.addEventListener('
|
|
675
|
+
rotationAnchor.addEventListener('pointerdown', startRotation);
|
|
676
|
+
rotationAnchor.addEventListener('pointerup', stopRotation);
|
|
677
677
|
|
|
678
678
|
rotationAnchor.addEventListener('mouseover', function () {
|
|
679
679
|
if (!isRotatingImage && !isDragging) {
|
|
@@ -708,8 +708,8 @@ function addAnchor(x, y, cursor) {
|
|
|
708
708
|
svg.appendChild(anchor);
|
|
709
709
|
|
|
710
710
|
// Add event listeners for dragging
|
|
711
|
-
anchor.addEventListener('
|
|
712
|
-
anchor.addEventListener('
|
|
711
|
+
anchor.addEventListener('pointerdown', startResize);
|
|
712
|
+
anchor.addEventListener('pointerup', stopResize);
|
|
713
713
|
|
|
714
714
|
}
|
|
715
715
|
|
|
@@ -739,13 +739,13 @@ function startResize(event) {
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
|
-
svg.addEventListener('
|
|
743
|
-
document.addEventListener('
|
|
742
|
+
svg.addEventListener('pointermove', resizeImage);
|
|
743
|
+
document.addEventListener('pointerup', stopResize);
|
|
744
744
|
}
|
|
745
745
|
|
|
746
746
|
function stopResize(event) {
|
|
747
747
|
stopInteracting(); // Call the combined stop function
|
|
748
|
-
document.removeEventListener('
|
|
748
|
+
document.removeEventListener('pointerup', stopResize); // Remove the global mouseup listener
|
|
749
749
|
}
|
|
750
750
|
|
|
751
751
|
function resizeImage(event) {
|
|
@@ -893,8 +893,8 @@ function stopRotation(event) {
|
|
|
893
893
|
isRotatingImage = false;
|
|
894
894
|
startRotationMouseAngle = null;
|
|
895
895
|
startImageRotation = null;
|
|
896
|
-
svg.removeEventListener('
|
|
897
|
-
document.removeEventListener('
|
|
896
|
+
svg.removeEventListener('pointermove', rotateImage);
|
|
897
|
+
document.removeEventListener('pointerup', stopRotation);
|
|
898
898
|
svg.style.cursor = 'default';
|
|
899
899
|
}
|
|
900
900
|
|
|
@@ -941,8 +941,8 @@ function startDrag(event) {
|
|
|
941
941
|
dragOffsetX = x - parseFloat(selectedImage.getAttribute('x'));
|
|
942
942
|
dragOffsetY = y - parseFloat(selectedImage.getAttribute('y'));
|
|
943
943
|
|
|
944
|
-
svg.addEventListener('
|
|
945
|
-
document.addEventListener('
|
|
944
|
+
svg.addEventListener('pointermove', dragImage);
|
|
945
|
+
document.addEventListener('pointerup', stopDrag);
|
|
946
946
|
}
|
|
947
947
|
|
|
948
948
|
function dragImage(event) {
|
|
@@ -982,7 +982,7 @@ function dragImage(event) {
|
|
|
982
982
|
|
|
983
983
|
function stopDrag(event) {
|
|
984
984
|
stopInteracting(); // Call the combined stop function
|
|
985
|
-
document.removeEventListener('
|
|
985
|
+
document.removeEventListener('pointerup', stopDrag); // Remove the global mouseup listener
|
|
986
986
|
}
|
|
987
987
|
|
|
988
988
|
function startRotation(event) {
|
|
@@ -1008,8 +1008,8 @@ function startRotation(event) {
|
|
|
1008
1008
|
startRotationMouseAngle = Math.atan2(mouseY - centerY, mouseX - centerX) * 180 / Math.PI;
|
|
1009
1009
|
startImageRotation = imageRotation;
|
|
1010
1010
|
|
|
1011
|
-
svg.addEventListener('
|
|
1012
|
-
document.addEventListener('
|
|
1011
|
+
svg.addEventListener('pointermove', rotateImage);
|
|
1012
|
+
document.addEventListener('pointerup', stopRotation);
|
|
1013
1013
|
|
|
1014
1014
|
svg.style.cursor = 'grabbing';
|
|
1015
1015
|
}
|
|
@@ -1168,9 +1168,9 @@ function stopInteracting() {
|
|
|
1168
1168
|
|
|
1169
1169
|
isDragging = false;
|
|
1170
1170
|
isRotatingImage = false;
|
|
1171
|
-
svg.removeEventListener('
|
|
1172
|
-
svg.removeEventListener('
|
|
1173
|
-
svg.removeEventListener('
|
|
1171
|
+
svg.removeEventListener('pointermove', dragImage);
|
|
1172
|
+
svg.removeEventListener('pointermove', resizeImage);
|
|
1173
|
+
svg.removeEventListener('pointermove', rotateImage);
|
|
1174
1174
|
currentAnchor = null;
|
|
1175
1175
|
startRotationMouseAngle = null;
|
|
1176
1176
|
startImageRotation = null;
|
package/src/tools/textTool.js
CHANGED
|
@@ -302,10 +302,10 @@ function makeTextEditable(textElement, groupElement) {
|
|
|
302
302
|
const handleClickOutside = (event) => {
|
|
303
303
|
if (!input.contains(event.target)) {
|
|
304
304
|
renderText(input, textElement, true);
|
|
305
|
-
document.removeEventListener('
|
|
305
|
+
document.removeEventListener('pointerdown', handleClickOutside, true);
|
|
306
306
|
}
|
|
307
307
|
};
|
|
308
|
-
document.addEventListener('
|
|
308
|
+
document.addEventListener('pointerdown', handleClickOutside, true);
|
|
309
309
|
input.handleClickOutside = handleClickOutside;
|
|
310
310
|
|
|
311
311
|
// Set text cursor on the element during edit mode
|
|
@@ -324,7 +324,7 @@ function renderText(input, textElement, deleteIfEmpty = false) {
|
|
|
324
324
|
const gElement = input.textGroup;
|
|
325
325
|
|
|
326
326
|
if (input.handleClickOutside) {
|
|
327
|
-
document.removeEventListener('
|
|
327
|
+
document.removeEventListener('pointerdown', input.handleClickOutside, true);
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
document.body.removeChild(input);
|
|
@@ -403,7 +403,8 @@ function renderText(input, textElement, deleteIfEmpty = false) {
|
|
|
403
403
|
// After rendering text, switch to selection tool and auto-select
|
|
404
404
|
if (gElement.parentNode) {
|
|
405
405
|
switchToSelectionTool();
|
|
406
|
-
|
|
406
|
+
// Defer selection so the tool switch (async React state) completes first
|
|
407
|
+
requestAnimationFrame(() => selectElement(gElement));
|
|
407
408
|
}
|
|
408
409
|
}
|
|
409
410
|
|
|
@@ -473,7 +474,7 @@ function createSelectionFeedback(groupElement) {
|
|
|
473
474
|
groupElement.appendChild(handleRect);
|
|
474
475
|
resizeHandles[handle.name] = handleRect;
|
|
475
476
|
|
|
476
|
-
handleRect.addEventListener('
|
|
477
|
+
handleRect.addEventListener('pointerdown', (e) => {
|
|
477
478
|
if (window.isSelectionToolActive) {
|
|
478
479
|
e.stopPropagation();
|
|
479
480
|
startResize(e, handle.name);
|
|
@@ -497,7 +498,7 @@ function createSelectionFeedback(groupElement) {
|
|
|
497
498
|
|
|
498
499
|
resizeHandles.rotate = rotationAnchor;
|
|
499
500
|
|
|
500
|
-
rotationAnchor.addEventListener('
|
|
501
|
+
rotationAnchor.addEventListener('pointerdown', (e) => {
|
|
501
502
|
if (window.isSelectionToolActive) {
|
|
502
503
|
e.stopPropagation();
|
|
503
504
|
startRotation(e);
|
|
@@ -611,8 +612,8 @@ function startRotation(event) {
|
|
|
611
612
|
|
|
612
613
|
svg.style.cursor = 'grabbing';
|
|
613
614
|
|
|
614
|
-
window.addEventListener('
|
|
615
|
-
window.addEventListener('
|
|
615
|
+
window.addEventListener('pointermove', handleMouseMove);
|
|
616
|
+
window.addEventListener('pointerup', handleMouseUp);
|
|
616
617
|
}
|
|
617
618
|
|
|
618
619
|
function removeSelectionFeedback(element) {
|
|
@@ -686,8 +687,8 @@ function deselectElement() {
|
|
|
686
687
|
rotationStartTransform = null;
|
|
687
688
|
svg.style.cursor = 'default';
|
|
688
689
|
|
|
689
|
-
window.removeEventListener('
|
|
690
|
-
window.removeEventListener('
|
|
690
|
+
window.removeEventListener('pointermove', handleMouseMove);
|
|
691
|
+
window.removeEventListener('pointerup', handleMouseUp);
|
|
691
692
|
}
|
|
692
693
|
}
|
|
693
694
|
|
|
@@ -729,8 +730,8 @@ function startDrag(event) {
|
|
|
729
730
|
|
|
730
731
|
svg.style.cursor = 'grabbing';
|
|
731
732
|
|
|
732
|
-
svg.addEventListener('
|
|
733
|
-
svg.addEventListener('
|
|
733
|
+
svg.addEventListener('pointermove', handleMouseMove);
|
|
734
|
+
svg.addEventListener('pointerup', handleMouseUp);
|
|
734
735
|
}
|
|
735
736
|
|
|
736
737
|
function startResize(event, anchor) {
|
|
@@ -776,8 +777,8 @@ function startResize(event, anchor) {
|
|
|
776
777
|
|
|
777
778
|
svg.style.cursor = resizeHandles[anchor]?.style.cursor || 'default';
|
|
778
779
|
|
|
779
|
-
svg.addEventListener('
|
|
780
|
-
svg.addEventListener('
|
|
780
|
+
svg.addEventListener('pointermove', handleMouseMove);
|
|
781
|
+
svg.addEventListener('pointerup', handleMouseUp);
|
|
781
782
|
}
|
|
782
783
|
|
|
783
784
|
|
|
@@ -1152,10 +1153,10 @@ const handleMouseUp = (event) => {
|
|
|
1152
1153
|
setTimeout(updateSelectionFeedback, 0);
|
|
1153
1154
|
}
|
|
1154
1155
|
|
|
1155
|
-
svg.removeEventListener('
|
|
1156
|
-
svg.removeEventListener('
|
|
1157
|
-
window.removeEventListener('
|
|
1158
|
-
window.removeEventListener('
|
|
1156
|
+
svg.removeEventListener('pointermove', handleMouseMove);
|
|
1157
|
+
svg.removeEventListener('pointerup', handleMouseUp);
|
|
1158
|
+
window.removeEventListener('pointermove', handleMouseMove);
|
|
1159
|
+
window.removeEventListener('pointerup', handleMouseUp);
|
|
1159
1160
|
};
|
|
1160
1161
|
|
|
1161
1162
|
function extractRotationFromTransform(element) {
|
|
@@ -1169,6 +1170,7 @@ function extractRotationFromTransform(element) {
|
|
|
1169
1170
|
|
|
1170
1171
|
// EXPORTED EVENT HANDLERS
|
|
1171
1172
|
const handleTextMouseDown = function (e) {
|
|
1173
|
+
if (!e.target) return;
|
|
1172
1174
|
const activeEditor = document.querySelector("textarea.svg-text-editor");
|
|
1173
1175
|
if (activeEditor && activeEditor.contains(e.target)) {
|
|
1174
1176
|
return;
|
|
@@ -1263,14 +1265,14 @@ const handleTextMouseMove = function (e) {
|
|
|
1263
1265
|
|
|
1264
1266
|
// Handle cursor changes for text tool
|
|
1265
1267
|
if (isTextToolActive) {
|
|
1266
|
-
const targetGroup = e.target
|
|
1268
|
+
const targetGroup = e.target?.closest?.('g[data-type="text-group"]');
|
|
1267
1269
|
if (targetGroup) {
|
|
1268
1270
|
svg.style.cursor = 'pointer';
|
|
1269
1271
|
} else {
|
|
1270
1272
|
svg.style.cursor = 'crosshair';
|
|
1271
1273
|
}
|
|
1272
1274
|
} else if (isSelectionToolActive) {
|
|
1273
|
-
const targetGroup = e.target
|
|
1275
|
+
const targetGroup = e.target?.closest?.('g[data-type="text-group"]');
|
|
1274
1276
|
if (targetGroup) {
|
|
1275
1277
|
svg.style.cursor = 'default';
|
|
1276
1278
|
}
|
|
@@ -1797,8 +1799,9 @@ window.updateSelectedTextStyle = function(changes) {
|
|
|
1797
1799
|
}
|
|
1798
1800
|
};
|
|
1799
1801
|
|
|
1800
|
-
// Expose
|
|
1802
|
+
// Expose select/deselect for external callers (Selection.js, TextShape.selectShape)
|
|
1801
1803
|
window.__deselectTextElement = deselectElement;
|
|
1804
|
+
window.__selectTextElement = selectElement;
|
|
1802
1805
|
|
|
1803
1806
|
// React sidebar bridge — text ↔ code conversion
|
|
1804
1807
|
window.__convertTextToCode = function() {
|