@teachinglab/omd 0.7.4 → 0.7.6

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,11 +41,13 @@ export class SelectTool extends Tool {
41
41
  /** @private - OMD dragging state */
42
42
  this.isDraggingOMD = false;
43
43
  this.draggedOMDElement = null;
44
+ this.selectedOMDElements = new Set();
44
45
 
45
46
  /** @private - Stroke dragging state */
46
47
  this.isDraggingStrokes = false;
47
48
  this.dragStartPoint = null;
48
49
  this.potentialDeselect = null;
50
+ this.hasSeparatedForDrag = false;
49
51
 
50
52
  // Initialize resize handle manager for OMD visuals
51
53
  this.resizeHandleManager = new ResizeHandleManager(canvas);
@@ -89,6 +91,7 @@ export class SelectTool extends Tool {
89
91
  if (isSelected) {
90
92
  // Already selected - prepare for drag, but don't deselect yet
91
93
  this.isDraggingStrokes = true;
94
+ this.hasSeparatedForDrag = false;
92
95
  this.dragStartPoint = { x: event.x, y: event.y };
93
96
  this.potentialDeselect = segmentSelection;
94
97
 
@@ -98,12 +101,16 @@ export class SelectTool extends Tool {
98
101
  }
99
102
  return;
100
103
  } else {
101
- // Clicking on a stroke segment - clear OMD selection and handle segment selection
102
- this.resizeHandleManager.clearSelection();
104
+ // Clicking on a stroke segment
105
+ if (!event.shiftKey) {
106
+ this.resizeHandleManager.clearSelection();
107
+ this.selectedOMDElements.clear();
108
+ }
103
109
  this._handleSegmentClick(segmentSelection, event.shiftKey);
104
110
 
105
111
  // Prepare for drag immediately after selection
106
112
  this.isDraggingStrokes = true;
113
+ this.hasSeparatedForDrag = false;
107
114
  this.dragStartPoint = { x: event.x, y: event.y };
108
115
 
109
116
  if (this.canvas.eventManager) {
@@ -111,14 +118,33 @@ export class SelectTool extends Tool {
111
118
  }
112
119
  }
113
120
  } else if (omdElement) {
114
- // Clicking on an OMD visual - clear segment selection and select OMD
121
+ // Clicking on an OMD visual
122
+
123
+ // Check if already selected
124
+ if (this.selectedOMDElements.has(omdElement)) {
125
+ // Already selected - prepare for drag
126
+ this.isDraggingOMD = true;
127
+ this.draggedOMDElement = omdElement; // Primary drag target
128
+ this.startPoint = { x: event.x, y: event.y };
129
+
130
+ if (this.canvas.eventManager) {
131
+ this.canvas.eventManager.isDrawing = true;
132
+ }
133
+ return;
134
+ }
135
+
136
+ // New selection
115
137
  if (!event.shiftKey) {
116
138
  this.selectedSegments.clear();
117
139
  this._clearSelectionVisuals();
140
+ this.selectedOMDElements.clear();
141
+ this.resizeHandleManager.clearSelection();
118
142
  }
143
+
144
+ this.selectedOMDElements.add(omdElement);
119
145
  this.resizeHandleManager.selectElement(omdElement);
120
146
 
121
- // CRITICAL: Start tracking for potential drag operation
147
+ // Start tracking for potential drag operation
122
148
  this.isDraggingOMD = true;
123
149
  this.draggedOMDElement = omdElement;
124
150
  this.startPoint = { x: event.x, y: event.y };
@@ -128,11 +154,32 @@ export class SelectTool extends Tool {
128
154
  this.canvas.eventManager.isDrawing = true;
129
155
  }
130
156
 
131
- // Don't start box selection - we're either resizing or will be dragging
132
157
  return;
133
158
  } else {
159
+ // Check if clicking inside existing selection bounds
160
+ const selectionBounds = this._getSelectionBounds();
161
+ if (selectionBounds &&
162
+ event.x >= selectionBounds.x &&
163
+ event.x <= selectionBounds.x + selectionBounds.width &&
164
+ event.y >= selectionBounds.y &&
165
+ event.y <= selectionBounds.y + selectionBounds.height) {
166
+
167
+ // Drag the selection (strokes AND OMD elements)
168
+ this.isDraggingStrokes = true; // We reuse this flag for general dragging
169
+ this.isDraggingOMD = true; // Also set this for OMD elements
170
+ this.hasSeparatedForDrag = false;
171
+ this.dragStartPoint = { x: event.x, y: event.y };
172
+ this.startPoint = { x: event.x, y: event.y }; // For OMD dragging
173
+
174
+ if (this.canvas.eventManager) {
175
+ this.canvas.eventManager.isDrawing = true;
176
+ }
177
+ return;
178
+ }
179
+
134
180
  // Clicking on empty space - clear all selections and start box selection
135
181
  this.resizeHandleManager.clearSelection();
182
+ this.selectedOMDElements.clear();
136
183
  this._startBoxSelection(event.x, event.y, event.shiftKey);
137
184
 
138
185
  // CRITICAL: Set startPoint AFTER _startBoxSelection so it doesn't get cleared!
@@ -156,10 +203,12 @@ export class SelectTool extends Tool {
156
203
  return;
157
204
  }
158
205
 
206
+ let handled = false;
207
+
159
208
  // Handle OMD dragging if in progress
160
- if (this.isDraggingOMD && this.draggedOMDElement) {
161
- this._dragOMDElement(event.x, event.y);
162
- return;
209
+ if (this.isDraggingOMD) {
210
+ this._dragOMDElements(event.x, event.y);
211
+ handled = true;
163
212
  }
164
213
 
165
214
  // Handle stroke dragging if in progress
@@ -167,32 +216,40 @@ export class SelectTool extends Tool {
167
216
  const dx = event.x - this.dragStartPoint.x;
168
217
  const dy = event.y - this.dragStartPoint.y;
169
218
 
170
- if (dx === 0 && dy === 0) return;
171
-
172
- // If we moved, it's a drag, so cancel potential deselect
173
- this.potentialDeselect = null;
174
-
175
- // Move all selected strokes
176
- const movedStrokes = new Set();
177
- for (const [strokeId, _] of this.selectedSegments) {
178
- const stroke = this.canvas.strokes.get(strokeId);
179
- if (stroke) {
180
- stroke.move(dx, dy);
181
- movedStrokes.add(strokeId);
219
+ if (dx !== 0 || dy !== 0) {
220
+ // If we moved, it's a drag, so cancel potential deselect
221
+ this.potentialDeselect = null;
222
+
223
+ // Separate selected parts if needed
224
+ if (!this.hasSeparatedForDrag) {
225
+ this._separateSelectedParts();
226
+ this.hasSeparatedForDrag = true;
182
227
  }
228
+
229
+ // Move all selected strokes
230
+ const movedStrokes = new Set();
231
+ for (const [strokeId, _] of this.selectedSegments) {
232
+ const stroke = this.canvas.strokes.get(strokeId);
233
+ if (stroke) {
234
+ stroke.move(dx, dy);
235
+ movedStrokes.add(strokeId);
236
+ }
237
+ }
238
+
239
+ this.dragStartPoint = { x: event.x, y: event.y };
240
+ this._updateSegmentSelectionVisuals();
241
+
242
+ // Emit event
243
+ this.canvas.emit('strokesMoved', {
244
+ dx, dy,
245
+ strokeIds: Array.from(movedStrokes)
246
+ });
183
247
  }
184
-
185
- this.dragStartPoint = { x: event.x, y: event.y };
186
- this._updateSegmentSelectionVisuals();
187
-
188
- // Emit event
189
- this.canvas.emit('strokesMoved', {
190
- dx, dy,
191
- strokeIds: Array.from(movedStrokes)
192
- });
193
- return;
248
+ handled = true;
194
249
  }
195
250
 
251
+ if (handled) return;
252
+
196
253
  // Handle box selection if in progress
197
254
  if (!this.isSelecting || !this.selectionBox) return;
198
255
 
@@ -380,6 +437,7 @@ export class SelectTool extends Tool {
380
437
  this.selectedSegments.clear();
381
438
 
382
439
  // Clear OMD selection
440
+ this.selectedOMDElements.clear();
383
441
  this.resizeHandleManager.clearSelection();
384
442
 
385
443
  // Remove selection box if it exists
@@ -485,19 +543,97 @@ export class SelectTool extends Tool {
485
543
  }
486
544
 
487
545
  /**
488
- * Drags the selected OMD element
546
+ * Gets the bounding box of the current selection (strokes + OMD).
547
+ * @private
548
+ * @returns {{x: number, y: number, width: number, height: number}|null}
549
+ */
550
+ _getSelectionBounds() {
551
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
552
+ let hasPoints = false;
553
+
554
+ // 1. Check strokes
555
+ if (this.selectedSegments.size > 0) {
556
+ for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
557
+ const stroke = this.canvas.strokes.get(strokeId);
558
+ if (!stroke || !stroke.points) continue;
559
+
560
+ for (const segmentIndex of segmentSet) {
561
+ if (segmentIndex >= stroke.points.length - 1) continue;
562
+ const p1 = stroke.points[segmentIndex];
563
+ const p2 = stroke.points[segmentIndex + 1];
564
+
565
+ minX = Math.min(minX, p1.x, p2.x);
566
+ minY = Math.min(minY, p1.y, p2.y);
567
+ maxX = Math.max(maxX, p1.x, p2.x);
568
+ maxY = Math.max(maxY, p1.y, p2.y);
569
+ hasPoints = true;
570
+ }
571
+ }
572
+ }
573
+
574
+ // 2. Check OMD elements
575
+ if (this.selectedOMDElements.size > 0) {
576
+ for (const element of this.selectedOMDElements) {
577
+ const bbox = this._getOMDElementBounds(element);
578
+ if (bbox) {
579
+ minX = Math.min(minX, bbox.x);
580
+ minY = Math.min(minY, bbox.y);
581
+ maxX = Math.max(maxX, bbox.x + bbox.width);
582
+ maxY = Math.max(maxY, bbox.y + bbox.height);
583
+ hasPoints = true;
584
+ }
585
+ }
586
+ }
587
+
588
+ if (!hasPoints) return null;
589
+
590
+ // Add padding to match the visual box
591
+ const padding = 8;
592
+ return {
593
+ x: minX - padding,
594
+ y: minY - padding,
595
+ width: (maxX + padding) - (minX - padding),
596
+ height: (maxY + padding) - (minY - padding)
597
+ };
598
+ }
599
+
600
+ /**
601
+ * Drags all selected OMD elements
489
602
  * @private
490
603
  * @param {number} x - Current pointer x coordinate
491
604
  * @param {number} y - Current pointer y coordinate
492
605
  */
493
- _dragOMDElement(x, y) {
494
- if (!this.draggedOMDElement || !this.startPoint) return;
606
+ _dragOMDElements(x, y) {
607
+ if (!this.startPoint) return;
495
608
 
496
609
  const dx = x - this.startPoint.x;
497
610
  const dy = y - this.startPoint.y;
498
611
 
612
+ if (dx === 0 && dy === 0) return;
613
+
614
+ for (const element of this.selectedOMDElements) {
615
+ this._moveOMDElement(element, dx, dy);
616
+ }
617
+
618
+ // Update start point for next move
619
+ this.startPoint = { x, y };
620
+
621
+ // Update resize handles if we have a single selection
622
+ if (this.selectedOMDElements.size === 1) {
623
+ const element = this.selectedOMDElements.values().next().value;
624
+ this.resizeHandleManager.updateIfSelected(element);
625
+ }
626
+
627
+ this._updateSegmentSelectionVisuals();
628
+ }
629
+
630
+ /**
631
+ * Moves a single OMD element
632
+ * @private
633
+ */
634
+ _moveOMDElement(element, dx, dy) {
499
635
  // Parse current transform
500
- const currentTransform = this.draggedOMDElement.getAttribute('transform') || '';
636
+ const currentTransform = element.getAttribute('transform') || '';
501
637
  const translateMatch = currentTransform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
502
638
  const scaleMatch = currentTransform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
503
639
 
@@ -517,13 +653,40 @@ export class SelectTool extends Tool {
517
653
  newTransform += ` scale(${scaleX}, ${scaleY})`;
518
654
  }
519
655
 
520
- this.draggedOMDElement.setAttribute('transform', newTransform);
521
-
522
- // Update start point for next move
523
- this.startPoint = { x, y };
524
-
525
- // Update resize handles if element is selected
526
- this.resizeHandleManager.updateIfSelected(this.draggedOMDElement);
656
+ element.setAttribute('transform', newTransform);
657
+ }
658
+
659
+ /**
660
+ * Gets the bounds of an OMD element including transform
661
+ * @private
662
+ */
663
+ _getOMDElementBounds(item) {
664
+ try {
665
+ const bbox = item.getBBox();
666
+ const transform = item.getAttribute('transform') || '';
667
+ let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
668
+
669
+ const translateMatch = transform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
670
+ if (translateMatch) {
671
+ offsetX = parseFloat(translateMatch[1]) || 0;
672
+ offsetY = parseFloat(translateMatch[2]) || 0;
673
+ }
674
+
675
+ const scaleMatch = transform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
676
+ if (scaleMatch) {
677
+ scaleX = parseFloat(scaleMatch[1]) || 1;
678
+ scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
679
+ }
680
+
681
+ return {
682
+ x: offsetX + (bbox.x * scaleX),
683
+ y: offsetY + (bbox.y * scaleY),
684
+ width: bbox.width * scaleX,
685
+ height: bbox.height * scaleY
686
+ };
687
+ } catch (e) {
688
+ return null;
689
+ }
527
690
  }
528
691
 
529
692
  /**
@@ -702,6 +865,7 @@ export class SelectTool extends Tool {
702
865
  const height = parseFloat(this.selectionBox.getAttribute('height'));
703
866
  const selectionBounds = new BoundingBox(x, y, width, height);
704
867
 
868
+ // 1. Select strokes
705
869
  for (const [id, stroke] of this.canvas.strokes) {
706
870
  if (!stroke.points || stroke.points.length < 2) continue;
707
871
  for (let i = 0; i < stroke.points.length - 1; i++) {
@@ -715,6 +879,36 @@ export class SelectTool extends Tool {
715
879
  }
716
880
  }
717
881
  }
882
+
883
+ // 2. Select OMD elements
884
+ const omdLayer = this.canvas.drawingLayer?.querySelector('.omd-layer');
885
+ if (omdLayer) {
886
+ const omdItems = omdLayer.querySelectorAll('.omd-item');
887
+ for (const item of omdItems) {
888
+ if (item?.dataset?.locked === 'true') continue;
889
+
890
+ const itemBounds = this._getOMDElementBounds(item);
891
+ if (itemBounds) {
892
+ // Check intersection
893
+ const intersects = !(itemBounds.x > x + width ||
894
+ itemBounds.x + itemBounds.width < x ||
895
+ itemBounds.y > y + height ||
896
+ itemBounds.y + itemBounds.height < y);
897
+
898
+ if (intersects) {
899
+ this.selectedOMDElements.add(item);
900
+ }
901
+ }
902
+ }
903
+ }
904
+
905
+ // Update resize handles
906
+ if (this.selectedOMDElements.size === 1 && this.selectedSegments.size === 0) {
907
+ this.resizeHandleManager.selectElement(this.selectedOMDElements.values().next().value);
908
+ } else {
909
+ this.resizeHandleManager.clearSelection();
910
+ }
911
+
718
912
  this._updateSegmentSelectionVisuals();
719
913
  }
720
914
 
@@ -775,37 +969,26 @@ export class SelectTool extends Tool {
775
969
  selectionLayer.removeChild(selectionLayer.firstChild);
776
970
  }
777
971
 
778
- // Add highlights for currently selected segments
779
- let highlightCount = 0;
780
-
781
- for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
782
- const stroke = this.canvas.strokes.get(strokeId);
783
- if (!stroke || !stroke.points) continue;
972
+ const bounds = this._getSelectionBounds();
973
+ if (!bounds) return;
784
974
 
785
- for (const segmentIndex of segmentSet) {
786
- if (segmentIndex >= stroke.points.length - 1) continue;
787
-
788
- const p1 = stroke.points[segmentIndex];
789
- const p2 = stroke.points[segmentIndex + 1];
790
-
791
- // Create highlight element
792
- const highlight = document.createElementNS('http://www.w3.org/2000/svg', 'line');
793
- highlight.setAttribute('x1', p1.x);
794
- highlight.setAttribute('y1', p1.y);
795
- highlight.setAttribute('x2', p2.x);
796
- highlight.setAttribute('y2', p2.y);
797
- highlight.setAttribute('stroke', omdColor.hiliteColor);
798
- highlight.setAttribute('stroke-width', '4');
799
- highlight.setAttribute('stroke-opacity', '0.8');
800
- highlight.setAttribute('stroke-linecap', 'round');
801
- highlight.style.pointerEvents = 'none';
802
- highlight.classList.add('selection-highlight');
803
-
804
- selectionLayer.appendChild(highlight);
805
- highlightCount++;
806
- }
807
- }
808
-
975
+ // Create bounding box element
976
+ const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
977
+ box.setAttribute('x', bounds.x);
978
+ box.setAttribute('y', bounds.y);
979
+ box.setAttribute('width', bounds.width);
980
+ box.setAttribute('height', bounds.height);
981
+ box.setAttribute('fill', 'none');
982
+ box.setAttribute('stroke', '#007bff'); // Selection color
983
+ box.setAttribute('stroke-width', '1.5');
984
+ box.setAttribute('stroke-dasharray', '6, 4'); // Dotted/Dashed
985
+ box.setAttribute('stroke-opacity', '0.6'); // Light
986
+ box.setAttribute('rx', '8'); // Rounded corners
987
+ box.setAttribute('ry', '8');
988
+ box.style.pointerEvents = 'none';
989
+ box.classList.add('selection-bounds');
990
+
991
+ selectionLayer.appendChild(box);
809
992
  }
810
993
 
811
994
  /**
@@ -819,12 +1002,13 @@ export class SelectTool extends Tool {
819
1002
  }
820
1003
 
821
1004
  /**
822
- * Selects all segments on the canvas.
1005
+ * Selects all segments and OMD elements on the canvas.
823
1006
  * @private
824
1007
  */
825
1008
  _selectAllSegments() {
826
1009
  // Clear current selection
827
1010
  this.selectedSegments.clear();
1011
+ this.selectedOMDElements.clear();
828
1012
 
829
1013
  let totalSegments = 0;
830
1014
 
@@ -843,6 +1027,17 @@ export class SelectTool extends Tool {
843
1027
  }
844
1028
  }
845
1029
 
1030
+ // Select all OMD elements
1031
+ const omdLayer = this.canvas.drawingLayer?.querySelector('.omd-layer');
1032
+ if (omdLayer) {
1033
+ const omdItems = omdLayer.querySelectorAll('.omd-item');
1034
+ for (const item of omdItems) {
1035
+ if (item?.dataset?.locked !== 'true') {
1036
+ this.selectedOMDElements.add(item);
1037
+ }
1038
+ }
1039
+ }
1040
+
846
1041
  // Update visuals
847
1042
  this._updateSegmentSelectionVisuals();
848
1043
 
@@ -854,42 +1049,55 @@ export class SelectTool extends Tool {
854
1049
  }
855
1050
 
856
1051
  /**
857
- * Deletes all currently selected segments efficiently.
1052
+ * Deletes all currently selected items (segments and OMD elements).
858
1053
  * @private
859
1054
  */
860
1055
  _deleteSelectedSegments() {
861
- if (this.selectedSegments.size === 0) return;
1056
+ let changed = false;
862
1057
 
863
- // Process each stroke that has selected segments
864
- const strokesToProcess = Array.from(this.selectedSegments.entries());
865
-
866
- for (const [strokeId, segmentIndices] of strokesToProcess) {
867
- const stroke = this.canvas.strokes.get(strokeId);
868
- if (!stroke || !stroke.points || stroke.points.length < 2) continue;
1058
+ // Delete OMD elements
1059
+ if (this.selectedOMDElements.size > 0) {
1060
+ for (const element of this.selectedOMDElements) {
1061
+ element.remove();
1062
+ }
1063
+ this.selectedOMDElements.clear();
1064
+ this.resizeHandleManager.clearSelection();
1065
+ changed = true;
1066
+ }
869
1067
 
870
- const sortedIndices = Array.from(segmentIndices).sort((a, b) => a - b);
1068
+ if (this.selectedSegments.size > 0) {
1069
+ // Process each stroke that has selected segments
1070
+ const strokesToProcess = Array.from(this.selectedSegments.entries());
871
1071
 
872
- // If all or most segments are selected, just delete the whole stroke
873
- const totalSegments = stroke.points.length - 1;
874
- const selectedCount = sortedIndices.length;
875
-
876
- if (selectedCount >= totalSegments * 0.8) {
877
- // Delete entire stroke
878
- this.canvas.removeStroke(strokeId);
879
- continue;
880
- }
1072
+ for (const [strokeId, segmentIndices] of strokesToProcess) {
1073
+ const stroke = this.canvas.strokes.get(strokeId);
1074
+ if (!stroke || !stroke.points || stroke.points.length < 2) continue;
881
1075
 
882
- // Split the stroke, keeping only unselected segments
883
- this._splitStrokeKeepingUnselected(stroke, sortedIndices);
1076
+ const sortedIndices = Array.from(segmentIndices).sort((a, b) => a - b);
1077
+
1078
+ // If all or most segments are selected, just delete the whole stroke
1079
+ const totalSegments = stroke.points.length - 1;
1080
+ const selectedCount = sortedIndices.length;
1081
+
1082
+ if (selectedCount >= totalSegments * 0.8) {
1083
+ // Delete entire stroke
1084
+ this.canvas.removeStroke(strokeId);
1085
+ continue;
1086
+ }
1087
+
1088
+ // Split the stroke, keeping only unselected segments
1089
+ this._splitStrokeKeepingUnselected(stroke, sortedIndices);
1090
+ }
1091
+ changed = true;
884
1092
  }
885
1093
 
886
- // Clear selection and update UI
887
- this.clearSelection();
888
-
889
- // Emit deletion event
890
- this.canvas.emit('segmentsDeleted', {
891
- strokesAffected: strokesToProcess.length
892
- });
1094
+ if (changed) {
1095
+ // Clear selection and update UI
1096
+ this.clearSelection();
1097
+
1098
+ // Emit deletion event
1099
+ this.canvas.emit('selectionDeleted');
1100
+ }
893
1101
  }
894
1102
 
895
1103
  /**
@@ -925,6 +1133,66 @@ export class SelectTool extends Tool {
925
1133
  });
926
1134
  }
927
1135
 
1136
+ /**
1137
+ * Separates selected segments into new strokes so they can be moved independently.
1138
+ * @private
1139
+ */
1140
+ _separateSelectedParts() {
1141
+ const newSelection = new Map();
1142
+ const strokesToProcess = Array.from(this.selectedSegments.entries());
1143
+
1144
+ for (const [strokeId, selectedIndices] of strokesToProcess) {
1145
+ const stroke = this.canvas.strokes.get(strokeId);
1146
+ if (!stroke || !stroke.points || stroke.points.length < 2) continue;
1147
+
1148
+ const totalSegments = stroke.points.length - 1;
1149
+
1150
+ // If fully selected, just keep it as is
1151
+ if (selectedIndices.size === totalSegments) {
1152
+ newSelection.set(strokeId, selectedIndices);
1153
+ continue;
1154
+ }
1155
+
1156
+ // It's a partial selection - we need to split
1157
+ const sortedSelectedIndices = Array.from(selectedIndices).sort((a, b) => a - b);
1158
+
1159
+ // Determine unselected indices
1160
+ const unselectedIndices = [];
1161
+ for (let i = 0; i < totalSegments; i++) {
1162
+ if (!selectedIndices.has(i)) {
1163
+ unselectedIndices.push(i);
1164
+ }
1165
+ }
1166
+
1167
+ // Group segments
1168
+ const selectedGroups = this._groupConsecutiveSegments(sortedSelectedIndices);
1169
+ const unselectedGroups = this._groupConsecutiveSegments(unselectedIndices);
1170
+
1171
+ // Create new strokes for selected parts
1172
+ selectedGroups.forEach(group => {
1173
+ const newStroke = this._createStrokeFromSegments(stroke, group);
1174
+ if (newStroke) {
1175
+ // Add to new selection (all segments selected)
1176
+ const newIndices = new Set();
1177
+ for (let i = 0; i < newStroke.points.length - 1; i++) {
1178
+ newIndices.add(i);
1179
+ }
1180
+ newSelection.set(newStroke.id, newIndices);
1181
+ }
1182
+ });
1183
+
1184
+ // Create new strokes for unselected parts (don't add to selection)
1185
+ unselectedGroups.forEach(group => {
1186
+ this._createStrokeFromSegments(stroke, group);
1187
+ });
1188
+
1189
+ // Remove original stroke
1190
+ this.canvas.removeStroke(strokeId);
1191
+ }
1192
+
1193
+ this.selectedSegments = newSelection;
1194
+ }
1195
+
928
1196
  /**
929
1197
  * Groups consecutive segment indices into separate arrays.
930
1198
  * @private
@@ -958,9 +1226,10 @@ export class SelectTool extends Tool {
958
1226
  /**
959
1227
  * Creates a new stroke from a group of segment indices.
960
1228
  * @private
1229
+ * @returns {Stroke|null} The newly created stroke
961
1230
  */
962
1231
  _createStrokeFromSegments(originalStroke, segmentIndices) {
963
- if (segmentIndices.length === 0) return;
1232
+ if (segmentIndices.length === 0) return null;
964
1233
 
965
1234
  // Collect points for the new stroke
966
1235
  const newPoints = [];
@@ -974,7 +1243,7 @@ export class SelectTool extends Tool {
974
1243
  }
975
1244
 
976
1245
  // Only create stroke if we have at least 2 points
977
- if (newPoints.length < 2) return;
1246
+ if (newPoints.length < 2) return null;
978
1247
 
979
1248
  // Create new stroke with same properties
980
1249
  const newStroke = new Stroke({
@@ -991,6 +1260,7 @@ export class SelectTool extends Tool {
991
1260
 
992
1261
  newStroke.finish();
993
1262
  this.canvas.addStroke(newStroke);
1263
+ return newStroke;
994
1264
  }
995
1265
 
996
1266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",