@elixpo/lixsketch 5.4.0 → 5.4.1
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/core/EventDispatcher.js +24 -11
- package/src/core/Selection.js +36 -27
- package/src/shapes/Arrow.js +32 -0
- package/src/shapes/FreehandStroke.js +31 -55
- package/src/tools/freehandTool.js +45 -3
package/package.json
CHANGED
|
@@ -75,9 +75,22 @@ let _documentDragActive = false;
|
|
|
75
75
|
|
|
76
76
|
function _onDocumentDragMove(e) {
|
|
77
77
|
_lastDragEvent = e;
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
78
|
+
// Clamp mouse coordinates to SVG bounds before forwarding to tool handlers,
|
|
79
|
+
// so getSVGCoordsFromMouse doesn't produce extreme values when cursor is outside.
|
|
80
|
+
const rect = svg.getBoundingClientRect();
|
|
81
|
+
const clampedX = Math.max(rect.left, Math.min(rect.right, e.clientX));
|
|
82
|
+
const clampedY = Math.max(rect.top, Math.min(rect.bottom, e.clientY));
|
|
83
|
+
const clampedEvent = new MouseEvent(e.type, {
|
|
84
|
+
clientX: clampedX,
|
|
85
|
+
clientY: clampedY,
|
|
86
|
+
buttons: e.buttons,
|
|
87
|
+
button: e.button,
|
|
88
|
+
ctrlKey: e.ctrlKey,
|
|
89
|
+
shiftKey: e.shiftKey,
|
|
90
|
+
altKey: e.altKey,
|
|
91
|
+
metaKey: e.metaKey,
|
|
92
|
+
});
|
|
93
|
+
handleMainMouseMove(clampedEvent);
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
function _onDocumentDragUp(e) {
|
|
@@ -231,6 +244,14 @@ const handleMainMouseDown = (e) => {
|
|
|
231
244
|
};
|
|
232
245
|
|
|
233
246
|
const handleMainMouseMove = (e) => {
|
|
247
|
+
// Auto-scroll when dragging near/past viewport edges (checked first so early returns don't skip it)
|
|
248
|
+
if (e.buttons & 1) {
|
|
249
|
+
_lastDragEvent = e;
|
|
250
|
+
_startAutoScroll();
|
|
251
|
+
} else {
|
|
252
|
+
_stopAutoScroll();
|
|
253
|
+
}
|
|
254
|
+
|
|
234
255
|
if (isSquareToolActive) {
|
|
235
256
|
handleMouseMoveRect(e);
|
|
236
257
|
} else if (isArrowToolActive) {
|
|
@@ -311,14 +332,6 @@ const handleMainMouseMove = (e) => {
|
|
|
311
332
|
handleCodeMouseMove(e);
|
|
312
333
|
}
|
|
313
334
|
}
|
|
314
|
-
|
|
315
|
-
// Auto-scroll when dragging near/past viewport edges
|
|
316
|
-
if (e.buttons & 1) {
|
|
317
|
-
_lastDragEvent = e;
|
|
318
|
-
_startAutoScroll();
|
|
319
|
-
} else {
|
|
320
|
-
_stopAutoScroll();
|
|
321
|
-
}
|
|
322
335
|
};
|
|
323
336
|
|
|
324
337
|
const handleMainMouseUp = (e) => {
|
package/src/core/Selection.js
CHANGED
|
@@ -151,10 +151,10 @@ function isShapeInSelectionRect(shape, selectionBounds) {
|
|
|
151
151
|
break;
|
|
152
152
|
case 'freehandStroke':
|
|
153
153
|
shapeBounds = {
|
|
154
|
-
x: shape.
|
|
155
|
-
y: shape.
|
|
156
|
-
width: shape.
|
|
157
|
-
height: shape.
|
|
154
|
+
x: shape.x,
|
|
155
|
+
y: shape.y,
|
|
156
|
+
width: shape.width,
|
|
157
|
+
height: shape.height
|
|
158
158
|
};
|
|
159
159
|
break;
|
|
160
160
|
case 'text':
|
|
@@ -389,10 +389,10 @@ class MultiSelection {
|
|
|
389
389
|
};
|
|
390
390
|
case 'freehandStroke':
|
|
391
391
|
return {
|
|
392
|
-
x: shape.
|
|
393
|
-
y: shape.
|
|
394
|
-
width: shape.
|
|
395
|
-
height: shape.
|
|
392
|
+
x: shape.x,
|
|
393
|
+
y: shape.y,
|
|
394
|
+
width: shape.width,
|
|
395
|
+
height: shape.height
|
|
396
396
|
};
|
|
397
397
|
case 'text':
|
|
398
398
|
const textElement = shape.group ? shape.group.querySelector('text') : null;
|
|
@@ -728,7 +728,7 @@ class MultiSelection {
|
|
|
728
728
|
shape.updateBoundingBox();
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
-
const boundingBox =
|
|
731
|
+
const boundingBox = this.getShapeBounds(shape);
|
|
732
732
|
|
|
733
733
|
shapeData = {
|
|
734
734
|
x: boundingBox.x || 0,
|
|
@@ -1360,6 +1360,8 @@ createRotatedControls(angleDiff = 0) {
|
|
|
1360
1360
|
if (typeof shape.finalizeMove === 'function') {
|
|
1361
1361
|
shape.finalizeMove();
|
|
1362
1362
|
}
|
|
1363
|
+
// Restore isSelected flag (startDrag's removeSelection clears it)
|
|
1364
|
+
shape.isSelected = true;
|
|
1363
1365
|
});
|
|
1364
1366
|
|
|
1365
1367
|
this.initialPositions.clear();
|
|
@@ -1367,6 +1369,9 @@ createRotatedControls(angleDiff = 0) {
|
|
|
1367
1369
|
// Push undo for all moved shapes
|
|
1368
1370
|
this._pushUndoForAll();
|
|
1369
1371
|
|
|
1372
|
+
// Refresh bounds and controls after finalizeMove may have changed positions
|
|
1373
|
+
this.updateControls();
|
|
1374
|
+
|
|
1370
1375
|
if (typeof svg !== 'undefined') {
|
|
1371
1376
|
svg.style.cursor = 'default';
|
|
1372
1377
|
}
|
|
@@ -1420,28 +1425,26 @@ function handleMultiSelectionMouseDown(e) {
|
|
|
1420
1425
|
return true;
|
|
1421
1426
|
}
|
|
1422
1427
|
|
|
1423
|
-
if
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
clickedOnSelectedShape = shape;
|
|
1430
|
-
break;
|
|
1431
|
-
}
|
|
1428
|
+
// Check if clicking on any shape that's part of the selection
|
|
1429
|
+
let clickedOnSelectedShape = null;
|
|
1430
|
+
for (const shape of multiSelection.selectedShapes) {
|
|
1431
|
+
if (shape.contains && shape.contains(x, y)) {
|
|
1432
|
+
clickedOnSelectedShape = shape;
|
|
1433
|
+
break;
|
|
1432
1434
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1439
|
-
multiSelection.startDrag(e);
|
|
1435
|
+
}
|
|
1436
|
+
if (clickedOnSelectedShape) {
|
|
1437
|
+
// Ctrl+Click: toggle shape out of multi-selection
|
|
1438
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1439
|
+
multiSelection.removeShape(clickedOnSelectedShape);
|
|
1440
1440
|
return true;
|
|
1441
1441
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1442
|
+
multiSelection.startDrag(e);
|
|
1443
|
+
return true;
|
|
1444
1444
|
}
|
|
1445
|
+
|
|
1446
|
+
// If click is within the selection bounds but not on a shape,
|
|
1447
|
+
// fall through to allow clicking on other shapes or starting a new selection
|
|
1445
1448
|
}
|
|
1446
1449
|
|
|
1447
1450
|
// Check if clicking on individual shape anchors - let them handle it
|
|
@@ -1501,6 +1504,12 @@ function handleMultiSelectionMouseDown(e) {
|
|
|
1501
1504
|
return true;
|
|
1502
1505
|
}
|
|
1503
1506
|
|
|
1507
|
+
// If the clicked shape is part of the multi-selection, start dragging instead of clearing
|
|
1508
|
+
if (multiSelection.selectedShapes.size > 1 && multiSelection.selectedShapes.has(clickedShape)) {
|
|
1509
|
+
multiSelection.startDrag(e);
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1504
1513
|
// If it's the same shape that's already selected, let individual handlers manage it
|
|
1505
1514
|
if (currentShape === clickedShape) {
|
|
1506
1515
|
// Clear any other multi-selected shapes so only this one remains selected
|
package/src/shapes/Arrow.js
CHANGED
|
@@ -418,6 +418,37 @@ class Arrow {
|
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
_updateAnchorPositions() {
|
|
422
|
+
if (!this.anchors || this.anchors.length === 0) return;
|
|
423
|
+
|
|
424
|
+
const anchorSize = 5 / currentZoom;
|
|
425
|
+
let anchorPositions = [this.startPoint, this.endPoint];
|
|
426
|
+
|
|
427
|
+
if (this.arrowCurved === "curved" && this.controlPoint1 && this.controlPoint2) {
|
|
428
|
+
const midOnCurve = this.getCubicBezierPoint(0.5);
|
|
429
|
+
anchorPositions.push(midOnCurve);
|
|
430
|
+
} else if (this.arrowCurved === "elbow") {
|
|
431
|
+
const elbowXVal = this.elbowX !== null ? this.elbowX : (this.startPoint.x + this.endPoint.x) / 2;
|
|
432
|
+
const midY = (this.startPoint.y + this.endPoint.y) / 2;
|
|
433
|
+
anchorPositions.push({ x: elbowXVal, y: midY });
|
|
434
|
+
} else {
|
|
435
|
+
// straight — offset end anchor past arrowhead
|
|
436
|
+
const arrowAngle = Math.atan2(this.endPoint.y - this.startPoint.y, this.endPoint.x - this.startPoint.x);
|
|
437
|
+
const arrowHeadClearance = this.arrowHeadLength + anchorSize - 10;
|
|
438
|
+
anchorPositions[1] = {
|
|
439
|
+
x: this.endPoint.x + arrowHeadClearance * Math.cos(arrowAngle),
|
|
440
|
+
y: this.endPoint.y + arrowHeadClearance * Math.sin(arrowAngle)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
anchorPositions.forEach((point, index) => {
|
|
445
|
+
if (this.anchors[index]) {
|
|
446
|
+
this.anchors[index].setAttribute('cx', point.x);
|
|
447
|
+
this.anchors[index].setAttribute('cy', point.y);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
421
452
|
_updateLabelElement() {
|
|
422
453
|
if (!this.label) {
|
|
423
454
|
if (this.labelElement && this.labelElement.parentNode === this.group) {
|
|
@@ -1633,6 +1664,7 @@ static getFrameAttachmentPoint(point, frame, tolerance = 20) {
|
|
|
1633
1664
|
this._updatePathElement();
|
|
1634
1665
|
this._updateHitArea();
|
|
1635
1666
|
this._updateLabelElement();
|
|
1667
|
+
this._updateAnchorPositions();
|
|
1636
1668
|
|
|
1637
1669
|
// Only update frame containment if we're actively dragging the shape itself
|
|
1638
1670
|
// and not being moved by a parent frame
|
|
@@ -63,11 +63,14 @@ class FreehandStroke {
|
|
|
63
63
|
this.group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
64
64
|
this.anchors = [];
|
|
65
65
|
this.rotationAnchor = null;
|
|
66
|
+
this.rotationLine = null;
|
|
66
67
|
this.selectionPadding = 8;
|
|
67
68
|
this.selectionOutline = null;
|
|
68
69
|
this.boundingBox = { x: 0, y: 0, width: 0, height: 0 };
|
|
69
70
|
this.shapeName = "freehandStroke";
|
|
70
|
-
|
|
71
|
+
this._moveOffsetX = 0;
|
|
72
|
+
this._moveOffsetY = 0;
|
|
73
|
+
|
|
71
74
|
// Frame attachment properties
|
|
72
75
|
this.parentFrame = null;
|
|
73
76
|
|
|
@@ -76,25 +79,25 @@ class FreehandStroke {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// Add position and dimension properties for frame compatibility
|
|
82
|
+
// Getters include pending move offset so callers see the visual position
|
|
79
83
|
get x() {
|
|
80
|
-
return this.boundingBox.x;
|
|
84
|
+
return this.boundingBox.x + (this._moveOffsetX || 0);
|
|
81
85
|
}
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
set x(value) {
|
|
84
|
-
const dx = value - this.boundingBox.x;
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
this.boundingBox.x = value;
|
|
88
|
+
const dx = value - this.boundingBox.x - (this._moveOffsetX || 0);
|
|
89
|
+
this.points = this.points.map(point => [point[0] + dx, point[1], point[2] || 0.5]);
|
|
90
|
+
this.boundingBox.x = value - (this._moveOffsetX || 0);
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
|
|
90
93
|
get y() {
|
|
91
|
-
return this.boundingBox.y;
|
|
94
|
+
return this.boundingBox.y + (this._moveOffsetY || 0);
|
|
92
95
|
}
|
|
93
|
-
|
|
96
|
+
|
|
94
97
|
set y(value) {
|
|
95
|
-
const dy = value - this.boundingBox.y;
|
|
98
|
+
const dy = value - this.boundingBox.y - (this._moveOffsetY || 0);
|
|
96
99
|
this.points = this.points.map(point => [point[0], point[1] + dy, point[2] || 0.5]);
|
|
97
|
-
this.boundingBox.y = value;
|
|
100
|
+
this.boundingBox.y = value - (this._moveOffsetY || 0);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
get width() {
|
|
@@ -343,14 +346,6 @@ class FreehandStroke {
|
|
|
343
346
|
const centerY = this.boundingBox.y + this.boundingBox.height / 2;
|
|
344
347
|
const rot = this.rotation ? `rotate(${this.rotation} ${centerX} ${centerY})` : '';
|
|
345
348
|
this.group.setAttribute('transform', `translate(${this._moveOffsetX}, ${this._moveOffsetY}) ${rot}`);
|
|
346
|
-
|
|
347
|
-
// Only update frame containment if we're actively dragging the shape itself
|
|
348
|
-
// and not being moved by a parent frame
|
|
349
|
-
if (isDraggingStroke && !this.isBeingMovedByFrame) {
|
|
350
|
-
this.updateFrameContainment();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
this.updateAttachedArrows();
|
|
354
349
|
}
|
|
355
350
|
|
|
356
351
|
// Call after drag ends to bake the offset into actual point coordinates
|
|
@@ -366,37 +361,9 @@ class FreehandStroke {
|
|
|
366
361
|
}
|
|
367
362
|
|
|
368
363
|
updateAttachedArrows() {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
updateFrameContainment() {
|
|
373
|
-
// Don't update if we're being moved by a frame
|
|
374
|
-
if (this.isBeingMovedByFrame) return;
|
|
375
|
-
|
|
376
|
-
let targetFrame = null;
|
|
377
|
-
|
|
378
|
-
// Find which frame this shape is over
|
|
379
|
-
shapes.forEach(shape => {
|
|
380
|
-
if (shape.shapeName === 'frame' && shape.isShapeInFrame(this)) {
|
|
381
|
-
targetFrame = shape;
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
// If we have a parent frame and we're being dragged, temporarily remove clipping
|
|
386
|
-
if (this.parentFrame && isDraggingStroke) {
|
|
387
|
-
this.parentFrame.temporarilyRemoveFromFrame(this);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Update frame highlighting
|
|
391
|
-
if (hoveredFrameStroke && hoveredFrameStroke !== targetFrame) {
|
|
392
|
-
hoveredFrameStroke.removeHighlight();
|
|
364
|
+
if (typeof window.__updateArrowsForShape === 'function') {
|
|
365
|
+
window.__updateArrowsForShape(this);
|
|
393
366
|
}
|
|
394
|
-
|
|
395
|
-
if (targetFrame && targetFrame !== hoveredFrameStroke) {
|
|
396
|
-
targetFrame.highlightFrame();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
hoveredFrameStroke = targetFrame;
|
|
400
367
|
}
|
|
401
368
|
|
|
402
369
|
selectStroke() {
|
|
@@ -425,11 +392,15 @@ class FreehandStroke {
|
|
|
425
392
|
if (this.rotationAnchor && this.rotationAnchor.parentNode === this.group) {
|
|
426
393
|
this.group.removeChild(this.rotationAnchor);
|
|
427
394
|
}
|
|
395
|
+
if (this.rotationLine && this.rotationLine.parentNode === this.group) {
|
|
396
|
+
this.group.removeChild(this.rotationLine);
|
|
397
|
+
}
|
|
428
398
|
if (this.selectionOutline && this.selectionOutline.parentNode === this.group) {
|
|
429
399
|
this.group.removeChild(this.selectionOutline);
|
|
430
400
|
}
|
|
431
401
|
this.anchors = [];
|
|
432
402
|
this.rotationAnchor = null;
|
|
403
|
+
this.rotationLine = null;
|
|
433
404
|
this.selectionOutline = null;
|
|
434
405
|
this.isSelected = false;
|
|
435
406
|
}
|
|
@@ -465,10 +436,14 @@ class FreehandStroke {
|
|
|
465
436
|
const buffer = 10;
|
|
466
437
|
const anchorSize = 10 / currentZoom;
|
|
467
438
|
|
|
439
|
+
// Account for pending move offset
|
|
440
|
+
const ox = this._moveOffsetX || 0;
|
|
441
|
+
const oy = this._moveOffsetY || 0;
|
|
442
|
+
|
|
468
443
|
// Transform the input coordinates to account for rotation
|
|
469
|
-
const centerX = this.boundingBox.x + this.boundingBox.width / 2;
|
|
470
|
-
const centerY = this.boundingBox.y + this.boundingBox.height / 2;
|
|
471
|
-
|
|
444
|
+
const centerX = this.boundingBox.x + ox + this.boundingBox.width / 2;
|
|
445
|
+
const centerY = this.boundingBox.y + oy + this.boundingBox.height / 2;
|
|
446
|
+
|
|
472
447
|
// Rotate the mouse coordinates to the shape's local coordinate system
|
|
473
448
|
const angleRad = -this.rotation * Math.PI / 180;
|
|
474
449
|
const dx = x - centerX;
|
|
@@ -477,8 +452,8 @@ class FreehandStroke {
|
|
|
477
452
|
const localY = dx * Math.sin(angleRad) + dy * Math.cos(angleRad) + centerY;
|
|
478
453
|
|
|
479
454
|
// Check resize anchors in local space
|
|
480
|
-
const expandedX = this.boundingBox.x - this.selectionPadding;
|
|
481
|
-
const expandedY = this.boundingBox.y - this.selectionPadding;
|
|
455
|
+
const expandedX = this.boundingBox.x + ox - this.selectionPadding;
|
|
456
|
+
const expandedY = this.boundingBox.y + oy - this.selectionPadding;
|
|
482
457
|
const expandedWidth = this.boundingBox.width + 2 * this.selectionPadding;
|
|
483
458
|
const expandedHeight = this.boundingBox.height + 2 * this.selectionPadding;
|
|
484
459
|
|
|
@@ -624,6 +599,7 @@ class FreehandStroke {
|
|
|
624
599
|
rotationLine.setAttribute('stroke-dasharray', '2 2');
|
|
625
600
|
rotationLine.setAttribute('style', 'pointer-events: none;');
|
|
626
601
|
this.group.appendChild(rotationLine);
|
|
602
|
+
this.rotationLine = rotationLine;
|
|
627
603
|
|
|
628
604
|
// Show sidebar when anchors are added (when shape is selected)
|
|
629
605
|
disableAllSideBars();
|
|
@@ -4,6 +4,9 @@ import { pushCreateAction, pushDeleteAction, pushOptionsChangeAction, pushTransf
|
|
|
4
4
|
import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
|
|
5
5
|
import { calculateSnap, clearSnapGuides } from '../core/SnapGuides.js';
|
|
6
6
|
|
|
7
|
+
// Expose updateArrowsForShape globally so FreehandStroke.updateAttachedArrows() can call it
|
|
8
|
+
// (FreehandStroke.js is a separate module and cannot import freehandTool's local bindings)
|
|
9
|
+
window.__updateArrowsForShape = updateArrowsForShape;
|
|
7
10
|
|
|
8
11
|
const strokeColors = document.querySelectorAll(".strokeColors span");
|
|
9
12
|
const strokeThicknesses = document.querySelectorAll(".strokeThickness span");
|
|
@@ -80,6 +83,31 @@ function getSvgPathFromStroke(stroke) {
|
|
|
80
83
|
return pathData.join(' ');
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
// Frame containment check for a freehand stroke during drag.
|
|
87
|
+
// Lives here (not in FreehandStroke.js) because it needs isDraggingStroke & hoveredFrameStroke.
|
|
88
|
+
function updateFrameContainmentForStroke(shape) {
|
|
89
|
+
if (shape.isBeingMovedByFrame) return;
|
|
90
|
+
|
|
91
|
+
let targetFrame = null;
|
|
92
|
+
shapes.forEach(s => {
|
|
93
|
+
if (s.shapeName === 'frame' && s.isShapeInFrame(shape)) {
|
|
94
|
+
targetFrame = s;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (shape.parentFrame && isDraggingStroke) {
|
|
99
|
+
shape.parentFrame.temporarilyRemoveFromFrame(shape);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (hoveredFrameStroke && hoveredFrameStroke !== targetFrame) {
|
|
103
|
+
hoveredFrameStroke.removeHighlight();
|
|
104
|
+
}
|
|
105
|
+
if (targetFrame && targetFrame !== hoveredFrameStroke) {
|
|
106
|
+
targetFrame.highlightFrame();
|
|
107
|
+
}
|
|
108
|
+
hoveredFrameStroke = targetFrame;
|
|
109
|
+
}
|
|
110
|
+
|
|
83
111
|
// Delete functionality
|
|
84
112
|
function deleteCurrentShape() {
|
|
85
113
|
if (currentShape && currentShape.shapeName === 'freehandStroke') {
|
|
@@ -304,6 +332,14 @@ function handleMouseMove(e) {
|
|
|
304
332
|
startX = x;
|
|
305
333
|
startY = y;
|
|
306
334
|
|
|
335
|
+
// Frame containment update (must live here where isDraggingStroke is in scope)
|
|
336
|
+
if (!currentShape.isBeingMovedByFrame) {
|
|
337
|
+
updateFrameContainmentForStroke(currentShape);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Update arrows attached to this shape
|
|
341
|
+
currentShape.updateAttachedArrows();
|
|
342
|
+
|
|
307
343
|
// Snap guides
|
|
308
344
|
if (window.__sketchStoreApi && window.__sketchStoreApi.getState().snapToObjects) {
|
|
309
345
|
const snap = calculateSnap(currentShape, e.shiftKey, e.clientX, e.clientY);
|
|
@@ -334,7 +370,7 @@ function handleMouseUp(e) {
|
|
|
334
370
|
if (currentStroke && currentStroke.points.length >= 2) {
|
|
335
371
|
currentStroke.draw(); // Redraw with final smoothing
|
|
336
372
|
pushCreateAction(currentStroke);
|
|
337
|
-
|
|
373
|
+
|
|
338
374
|
// Check for frame containment and track attachment
|
|
339
375
|
const finalFrame = hoveredFrameStroke;
|
|
340
376
|
if (finalFrame) {
|
|
@@ -342,6 +378,12 @@ function handleMouseUp(e) {
|
|
|
342
378
|
// Track the attachment for undo
|
|
343
379
|
pushFrameAttachmentAction(finalFrame, currentStroke, 'attach', null);
|
|
344
380
|
}
|
|
381
|
+
|
|
382
|
+
// Auto-select the drawn stroke and switch to selection tool
|
|
383
|
+
const drawnShape = currentStroke;
|
|
384
|
+
if (window.__sketchStoreApi) window.__sketchStoreApi.setActiveTool('select', { afterDraw: true });
|
|
385
|
+
currentShape = drawnShape;
|
|
386
|
+
currentShape.selectStroke();
|
|
345
387
|
} else if (currentStroke) {
|
|
346
388
|
// Remove strokes that are too small
|
|
347
389
|
shapes.pop();
|
|
@@ -349,13 +391,13 @@ function handleMouseUp(e) {
|
|
|
349
391
|
currentStroke.group.parentNode.removeChild(currentStroke.group);
|
|
350
392
|
}
|
|
351
393
|
}
|
|
352
|
-
|
|
394
|
+
|
|
353
395
|
// Clear frame highlighting
|
|
354
396
|
if (hoveredFrameStroke) {
|
|
355
397
|
hoveredFrameStroke.removeHighlight();
|
|
356
398
|
hoveredFrameStroke = null;
|
|
357
399
|
}
|
|
358
|
-
|
|
400
|
+
|
|
359
401
|
currentStroke = null;
|
|
360
402
|
}
|
|
361
403
|
|