@elixpo/lixsketch 5.4.1 → 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.
- package/package.json +1 -1
- package/src/SketchEngine.js +7 -0
- package/src/core/CopyPaste.js +1 -1
- package/src/core/EventDispatcher.js +39 -20
- package/src/core/SceneSerializer.js +12 -4
- package/src/core/Selection.js +57 -26
- package/src/core/ZoomPan.js +15 -9
- package/src/shapes/Arrow.js +3 -3
- package/src/shapes/Circle.js +3 -3
- package/src/shapes/CodeShape.js +5 -4
- 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 +11 -5
- 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 +40 -23
package/src/tools/textTool.js
CHANGED
|
@@ -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;
|
|
@@ -301,10 +302,10 @@ function makeTextEditable(textElement, groupElement) {
|
|
|
301
302
|
const handleClickOutside = (event) => {
|
|
302
303
|
if (!input.contains(event.target)) {
|
|
303
304
|
renderText(input, textElement, true);
|
|
304
|
-
document.removeEventListener('
|
|
305
|
+
document.removeEventListener('pointerdown', handleClickOutside, true);
|
|
305
306
|
}
|
|
306
307
|
};
|
|
307
|
-
document.addEventListener('
|
|
308
|
+
document.addEventListener('pointerdown', handleClickOutside, true);
|
|
308
309
|
input.handleClickOutside = handleClickOutside;
|
|
309
310
|
|
|
310
311
|
// Set text cursor on the element during edit mode
|
|
@@ -323,7 +324,7 @@ function renderText(input, textElement, deleteIfEmpty = false) {
|
|
|
323
324
|
const gElement = input.textGroup;
|
|
324
325
|
|
|
325
326
|
if (input.handleClickOutside) {
|
|
326
|
-
document.removeEventListener('
|
|
327
|
+
document.removeEventListener('pointerdown', input.handleClickOutside, true);
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
document.body.removeChild(input);
|
|
@@ -402,7 +403,8 @@ function renderText(input, textElement, deleteIfEmpty = false) {
|
|
|
402
403
|
// After rendering text, switch to selection tool and auto-select
|
|
403
404
|
if (gElement.parentNode) {
|
|
404
405
|
switchToSelectionTool();
|
|
405
|
-
|
|
406
|
+
// Defer selection so the tool switch (async React state) completes first
|
|
407
|
+
requestAnimationFrame(() => selectElement(gElement));
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
410
|
|
|
@@ -472,7 +474,7 @@ function createSelectionFeedback(groupElement) {
|
|
|
472
474
|
groupElement.appendChild(handleRect);
|
|
473
475
|
resizeHandles[handle.name] = handleRect;
|
|
474
476
|
|
|
475
|
-
handleRect.addEventListener('
|
|
477
|
+
handleRect.addEventListener('pointerdown', (e) => {
|
|
476
478
|
if (window.isSelectionToolActive) {
|
|
477
479
|
e.stopPropagation();
|
|
478
480
|
startResize(e, handle.name);
|
|
@@ -496,7 +498,7 @@ function createSelectionFeedback(groupElement) {
|
|
|
496
498
|
|
|
497
499
|
resizeHandles.rotate = rotationAnchor;
|
|
498
500
|
|
|
499
|
-
rotationAnchor.addEventListener('
|
|
501
|
+
rotationAnchor.addEventListener('pointerdown', (e) => {
|
|
500
502
|
if (window.isSelectionToolActive) {
|
|
501
503
|
e.stopPropagation();
|
|
502
504
|
startRotation(e);
|
|
@@ -610,8 +612,8 @@ function startRotation(event) {
|
|
|
610
612
|
|
|
611
613
|
svg.style.cursor = 'grabbing';
|
|
612
614
|
|
|
613
|
-
window.addEventListener('
|
|
614
|
-
window.addEventListener('
|
|
615
|
+
window.addEventListener('pointermove', handleMouseMove);
|
|
616
|
+
window.addEventListener('pointerup', handleMouseUp);
|
|
615
617
|
}
|
|
616
618
|
|
|
617
619
|
function removeSelectionFeedback(element) {
|
|
@@ -685,8 +687,8 @@ function deselectElement() {
|
|
|
685
687
|
rotationStartTransform = null;
|
|
686
688
|
svg.style.cursor = 'default';
|
|
687
689
|
|
|
688
|
-
window.removeEventListener('
|
|
689
|
-
window.removeEventListener('
|
|
690
|
+
window.removeEventListener('pointermove', handleMouseMove);
|
|
691
|
+
window.removeEventListener('pointerup', handleMouseUp);
|
|
690
692
|
}
|
|
691
693
|
}
|
|
692
694
|
|
|
@@ -728,8 +730,8 @@ function startDrag(event) {
|
|
|
728
730
|
|
|
729
731
|
svg.style.cursor = 'grabbing';
|
|
730
732
|
|
|
731
|
-
svg.addEventListener('
|
|
732
|
-
svg.addEventListener('
|
|
733
|
+
svg.addEventListener('pointermove', handleMouseMove);
|
|
734
|
+
svg.addEventListener('pointerup', handleMouseUp);
|
|
733
735
|
}
|
|
734
736
|
|
|
735
737
|
function startResize(event, anchor) {
|
|
@@ -753,6 +755,10 @@ function startResize(event, anchor) {
|
|
|
753
755
|
|
|
754
756
|
startPoint = getSVGCoordinates(event, selectedElement);
|
|
755
757
|
|
|
758
|
+
// Freeze the group's screen CTM at resize start so mouse→local mapping stays stable
|
|
759
|
+
const groupScreenCTM = selectedElement.getScreenCTM();
|
|
760
|
+
initialInverseScreenCTM = groupScreenCTM ? groupScreenCTM.inverse() : null;
|
|
761
|
+
|
|
756
762
|
const currentTransform = selectedElement.transform.baseVal.consolidate();
|
|
757
763
|
initialGroupTx = currentTransform ? currentTransform.matrix.e : 0;
|
|
758
764
|
initialGroupTy = currentTransform ? currentTransform.matrix.f : 0;
|
|
@@ -771,8 +777,8 @@ function startResize(event, anchor) {
|
|
|
771
777
|
|
|
772
778
|
svg.style.cursor = resizeHandles[anchor]?.style.cursor || 'default';
|
|
773
779
|
|
|
774
|
-
svg.addEventListener('
|
|
775
|
-
svg.addEventListener('
|
|
780
|
+
svg.addEventListener('pointermove', handleMouseMove);
|
|
781
|
+
svg.addEventListener('pointerup', handleMouseUp);
|
|
776
782
|
}
|
|
777
783
|
|
|
778
784
|
|
|
@@ -828,7 +834,16 @@ const handleMouseMove = (event) => {
|
|
|
828
834
|
const textElement = selectedElement.querySelector('text');
|
|
829
835
|
if (!textElement || !startBBox || startFontSize === null || !startPoint || !initialHandlePosRelGroup) return;
|
|
830
836
|
|
|
831
|
-
|
|
837
|
+
// Use the frozen initial CTM so the mapping doesn't shift as we change the group transform
|
|
838
|
+
let currentPoint;
|
|
839
|
+
if (initialInverseScreenCTM) {
|
|
840
|
+
const pt = svg.createSVGPoint();
|
|
841
|
+
pt.x = event.clientX;
|
|
842
|
+
pt.y = event.clientY;
|
|
843
|
+
currentPoint = pt.matrixTransform(initialInverseScreenCTM);
|
|
844
|
+
} else {
|
|
845
|
+
currentPoint = getSVGCoordinates(event, selectedElement);
|
|
846
|
+
}
|
|
832
847
|
|
|
833
848
|
const startX = startBBox.x;
|
|
834
849
|
const startY = startBBox.y;
|
|
@@ -1138,10 +1153,10 @@ const handleMouseUp = (event) => {
|
|
|
1138
1153
|
setTimeout(updateSelectionFeedback, 0);
|
|
1139
1154
|
}
|
|
1140
1155
|
|
|
1141
|
-
svg.removeEventListener('
|
|
1142
|
-
svg.removeEventListener('
|
|
1143
|
-
window.removeEventListener('
|
|
1144
|
-
window.removeEventListener('
|
|
1156
|
+
svg.removeEventListener('pointermove', handleMouseMove);
|
|
1157
|
+
svg.removeEventListener('pointerup', handleMouseUp);
|
|
1158
|
+
window.removeEventListener('pointermove', handleMouseMove);
|
|
1159
|
+
window.removeEventListener('pointerup', handleMouseUp);
|
|
1145
1160
|
};
|
|
1146
1161
|
|
|
1147
1162
|
function extractRotationFromTransform(element) {
|
|
@@ -1155,6 +1170,7 @@ function extractRotationFromTransform(element) {
|
|
|
1155
1170
|
|
|
1156
1171
|
// EXPORTED EVENT HANDLERS
|
|
1157
1172
|
const handleTextMouseDown = function (e) {
|
|
1173
|
+
if (!e.target) return;
|
|
1158
1174
|
const activeEditor = document.querySelector("textarea.svg-text-editor");
|
|
1159
1175
|
if (activeEditor && activeEditor.contains(e.target)) {
|
|
1160
1176
|
return;
|
|
@@ -1249,14 +1265,14 @@ const handleTextMouseMove = function (e) {
|
|
|
1249
1265
|
|
|
1250
1266
|
// Handle cursor changes for text tool
|
|
1251
1267
|
if (isTextToolActive) {
|
|
1252
|
-
const targetGroup = e.target
|
|
1268
|
+
const targetGroup = e.target?.closest?.('g[data-type="text-group"]');
|
|
1253
1269
|
if (targetGroup) {
|
|
1254
1270
|
svg.style.cursor = 'pointer';
|
|
1255
1271
|
} else {
|
|
1256
1272
|
svg.style.cursor = 'crosshair';
|
|
1257
1273
|
}
|
|
1258
1274
|
} else if (isSelectionToolActive) {
|
|
1259
|
-
const targetGroup = e.target
|
|
1275
|
+
const targetGroup = e.target?.closest?.('g[data-type="text-group"]');
|
|
1260
1276
|
if (targetGroup) {
|
|
1261
1277
|
svg.style.cursor = 'default';
|
|
1262
1278
|
}
|
|
@@ -1783,8 +1799,9 @@ window.updateSelectedTextStyle = function(changes) {
|
|
|
1783
1799
|
}
|
|
1784
1800
|
};
|
|
1785
1801
|
|
|
1786
|
-
// Expose
|
|
1802
|
+
// Expose select/deselect for external callers (Selection.js, TextShape.selectShape)
|
|
1787
1803
|
window.__deselectTextElement = deselectElement;
|
|
1804
|
+
window.__selectTextElement = selectElement;
|
|
1788
1805
|
|
|
1789
1806
|
// React sidebar bridge — text ↔ code conversion
|
|
1790
1807
|
window.__convertTextToCode = function() {
|
|
@@ -1820,4 +1837,4 @@ window.__setCodeLanguage = function(lang) {
|
|
|
1820
1837
|
}
|
|
1821
1838
|
};
|
|
1822
1839
|
|
|
1823
|
-
export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement };
|
|
1840
|
+
export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement, enterEditMode };
|