@elixpo/lixsketch 5.4.2 → 5.4.3

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.
@@ -41,18 +41,16 @@ function removeSelection() {
41
41
  const svg = getSVGElement();
42
42
  if (!svg) return;
43
43
 
44
- const outline = svg.querySelector(".selection-outline");
45
- if (outline) {
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('mousedown', startDrag);
54
- selectedIcon.removeEventListener('mouseup', stopDrag);
55
- selectedIcon.removeEventListener('mouseleave', stopDrag);
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('mousemove', checkDragStartWithThreshold);
225
- document.removeEventListener('mouseup', cancelDragPrep);
226
- window.removeEventListener('mouseup', cancelDragPrep);
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('mouseup', cancelDragPrep);
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('mousemove', checkDragStartWithThreshold);
237
- document.addEventListener('mouseup', cancelDragPrep);
238
- window.addEventListener('mouseup', cancelDragPrep);
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('mouseup', cancelDragPrep);
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
- shapes.push(iconShape);
362
- console.log('Icon added to shapes array for arrow attachment and frame functionality');
363
- } else {
364
- console.warn('shapes array not found - arrows and frames may not work with icons');
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 (isSelectionToolActive) {
407
- const clickedElement = e.target;
408
- const isIconElement = clickedElement.closest('[type="icon"]');
409
- const isAnchorElement = clickedElement.classList.contains('resize-anchor') ||
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('mousemove', checkDragStart);
472
- document.removeEventListener('mouseup', cancelDragPrep);
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('mousemove', _pendingDragChecker);
479
+ document.removeEventListener('pointermove', _pendingDragChecker);
478
480
  _pendingDragChecker = null;
479
481
  }
480
- document.removeEventListener('mouseup', cancelDragPrep);
481
- window.removeEventListener('mouseup', cancelDragPrep);
482
+ document.removeEventListener('pointerup', cancelDragPrep);
483
+ window.removeEventListener('pointerup', cancelDragPrep);
482
484
 
483
485
  const svg = getSVGElement();
484
486
  if (svg) {
485
- svg.removeEventListener('mouseup', cancelDragPrep);
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('mousemove', dragIcon);
510
- document.addEventListener('mouseup', stopDrag);
511
+ document.addEventListener('pointermove', dragIcon);
512
+ document.addEventListener('pointerup', stopDrag);
511
513
 
512
- window.addEventListener('mouseup', stopDrag);
514
+ window.addEventListener('pointerup', stopDrag);
513
515
 
514
516
  const svg = getSVGElement();
515
517
  if (svg) {
516
- svg.addEventListener('mouseup', stopDrag);
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
- if (selectedIcon !== targetIcon) {
546
- if (selectedIcon) {
547
- removeSelection();
548
- }
547
+ // Always clean up previous selection before applying new one
548
+ if (selectedIcon) {
549
+ removeSelection();
550
+ }
549
551
 
550
- selectedIcon = targetIcon;
552
+ selectedIcon = targetIcon;
551
553
 
552
- if (!selectedIcon) {
553
- console.warn('Could not find icon to select');
554
- return;
555
- }
554
+ if (!selectedIcon) {
555
+ console.warn('Could not find icon to select');
556
+ return;
557
+ }
556
558
 
557
- const transform = selectedIcon.getAttribute('transform');
558
- if (transform) {
559
- const rotateMatch = transform.match(/rotate\(([^,\s]+)/);
560
- if (rotateMatch) {
561
- iconRotation = parseFloat(rotateMatch[1]);
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
- addSelectionOutline();
569
+ addSelectionOutline();
568
570
 
569
- originalX = parseFloat(selectedIcon.getAttribute('x')) || 0;
570
- originalY = parseFloat(selectedIcon.getAttribute('y')) || 0;
571
- originalWidth = parseFloat(selectedIcon.getAttribute('width')) || placedIconSize;
572
- originalHeight = parseFloat(selectedIcon.getAttribute('height')) || placedIconSize;
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
- // Set currentShape so EventDispatcher routes subsequent events to icon handler
575
- const iconShape = (typeof shapes !== 'undefined' && Array.isArray(shapes))
576
- ? shapes.find(s => s.shapeName === 'icon' && s.element === selectedIcon)
577
- : null;
578
- if (iconShape) {
579
- currentShape = iconShape;
580
- currentShape.isSelected = true;
581
- if (window.__showSidebarForShape) window.__showSidebarForShape('icon');
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('mousedown', startResize);
617
- anchor.addEventListener('mouseup', stopResize);
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('mousedown', startRotation);
646
- rotationAnchor.addEventListener('mouseup', stopRotation);
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
- const rotationAnchor = svg.querySelector(".rotation-anchor");
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('mousemove', resizeIcon);
696
+ svg.addEventListener('pointermove', resizeIcon);
699
697
  }
700
- document.addEventListener('mouseup', stopResize);
698
+ document.addEventListener('pointerup', stopResize);
701
699
  }
702
700
 
703
701
  function stopResize(event) {
704
702
  stopInteracting();
705
- document.removeEventListener('mouseup', stopResize);
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('mousemove', dragIcon);
879
- document.removeEventListener('mouseup', stopDrag);
880
- window.removeEventListener('mouseup', stopDrag);
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('mouseup', stopDrag);
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('mousemove', rotateIcon);
915
+ svg.addEventListener('pointermove', rotateIcon);
918
916
  svg.style.cursor = 'grabbing';
919
917
  }
920
- document.addEventListener('mouseup', stopRotation);
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('mousemove', rotateIcon);
966
+ svg.removeEventListener('pointermove', rotateIcon);
969
967
  svg.style.cursor = 'default';
970
968
  }
971
- document.removeEventListener('mouseup', stopRotation);
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('mousemove', dragIcon);
1064
- svg.removeEventListener('mousemove', resizeIcon);
1065
- svg.removeEventListener('mousemove', rotateIcon);
1061
+ svg.removeEventListener('pointermove', dragIcon);
1062
+ svg.removeEventListener('pointermove', resizeIcon);
1063
+ svg.removeEventListener('pointermove', rotateIcon);
1066
1064
  }
1067
1065
 
1068
- document.removeEventListener('mousemove', dragIcon);
1069
- document.removeEventListener('mousemove', resizeIcon);
1070
- document.removeEventListener('mousemove', rotateIcon);
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;
@@ -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('mousedown', startDrag);
541
- selectedImage.addEventListener('mouseup', stopDrag);
542
- selectedImage.addEventListener('mouseleave', stopDrag);
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('mousedown', startDrag);
615
- selectedImage.removeEventListener('mouseup', stopDrag);
616
- selectedImage.removeEventListener('mouseleave', stopDrag);
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('mousedown', startResize);
651
- anchor.addEventListener('mouseup', stopResize);
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('mousedown', startRotation);
676
- rotationAnchor.addEventListener('mouseup', stopRotation);
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('mousedown', startResize);
712
- anchor.addEventListener('mouseup', stopResize);
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('mousemove', resizeImage);
743
- document.addEventListener('mouseup', stopResize);
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('mouseup', stopResize); // Remove the global mouseup listener
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('mousemove', rotateImage);
897
- document.removeEventListener('mouseup', stopRotation);
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('mousemove', dragImage);
945
- document.addEventListener('mouseup', stopDrag);
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('mouseup', stopDrag); // Remove the global mouseup listener
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('mousemove', rotateImage);
1012
- document.addEventListener('mouseup', stopRotation);
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('mousemove', dragImage);
1172
- svg.removeEventListener('mousemove', resizeImage);
1173
- svg.removeEventListener('mousemove', rotateImage);
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;
@@ -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('mousedown', handleClickOutside, true);
305
+ document.removeEventListener('pointerdown', handleClickOutside, true);
306
306
  }
307
307
  };
308
- document.addEventListener('mousedown', handleClickOutside, true);
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('mousedown', input.handleClickOutside, true);
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
- selectElement(gElement);
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('mousedown', (e) => {
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('mousedown', (e) => {
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('mousemove', handleMouseMove);
615
- window.addEventListener('mouseup', handleMouseUp);
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('mousemove', handleMouseMove);
690
- window.removeEventListener('mouseup', handleMouseUp);
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('mousemove', handleMouseMove);
733
- svg.addEventListener('mouseup', handleMouseUp);
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('mousemove', handleMouseMove);
780
- svg.addEventListener('mouseup', handleMouseUp);
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('mousemove', handleMouseMove);
1156
- svg.removeEventListener('mouseup', handleMouseUp);
1157
- window.removeEventListener('mousemove', handleMouseMove);
1158
- window.removeEventListener('mouseup', handleMouseUp);
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.closest('g[data-type="text-group"]');
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.closest('g[data-type="text-group"]');
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 deselectElement for external callers (Selection.js blank canvas click)
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() {