@teachinglab/omd 0.7.4 → 0.7.5

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.
@@ -46,6 +46,7 @@ export class SelectTool extends Tool {
46
46
  this.isDraggingStrokes = false;
47
47
  this.dragStartPoint = null;
48
48
  this.potentialDeselect = null;
49
+ this.hasSeparatedForDrag = false;
49
50
 
50
51
  // Initialize resize handle manager for OMD visuals
51
52
  this.resizeHandleManager = new ResizeHandleManager(canvas);
@@ -89,6 +90,7 @@ export class SelectTool extends Tool {
89
90
  if (isSelected) {
90
91
  // Already selected - prepare for drag, but don't deselect yet
91
92
  this.isDraggingStrokes = true;
93
+ this.hasSeparatedForDrag = false;
92
94
  this.dragStartPoint = { x: event.x, y: event.y };
93
95
  this.potentialDeselect = segmentSelection;
94
96
 
@@ -104,6 +106,7 @@ export class SelectTool extends Tool {
104
106
 
105
107
  // Prepare for drag immediately after selection
106
108
  this.isDraggingStrokes = true;
109
+ this.hasSeparatedForDrag = false;
107
110
  this.dragStartPoint = { x: event.x, y: event.y };
108
111
 
109
112
  if (this.canvas.eventManager) {
@@ -171,6 +174,12 @@ export class SelectTool extends Tool {
171
174
 
172
175
  // If we moved, it's a drag, so cancel potential deselect
173
176
  this.potentialDeselect = null;
177
+
178
+ // Separate selected parts if needed
179
+ if (!this.hasSeparatedForDrag) {
180
+ this._separateSelectedParts();
181
+ this.hasSeparatedForDrag = true;
182
+ }
174
183
 
175
184
  // Move all selected strokes
176
185
  const movedStrokes = new Set();
@@ -775,9 +784,12 @@ export class SelectTool extends Tool {
775
784
  selectionLayer.removeChild(selectionLayer.firstChild);
776
785
  }
777
786
 
778
- // Add highlights for currently selected segments
779
- let highlightCount = 0;
780
-
787
+ if (this.selectedSegments.size === 0) return;
788
+
789
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
790
+ let hasSelection = false;
791
+
792
+ // Calculate bounding box of all selected segments
781
793
  for (const [strokeId, segmentSet] of this.selectedSegments.entries()) {
782
794
  const stroke = this.canvas.strokes.get(strokeId);
783
795
  if (!stroke || !stroke.points) continue;
@@ -788,24 +800,43 @@ export class SelectTool extends Tool {
788
800
  const p1 = stroke.points[segmentIndex];
789
801
  const p2 = stroke.points[segmentIndex + 1];
790
802
 
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++;
803
+ minX = Math.min(minX, p1.x, p2.x);
804
+ minY = Math.min(minY, p1.y, p2.y);
805
+ maxX = Math.max(maxX, p1.x, p2.x);
806
+ maxY = Math.max(maxY, p1.y, p2.y);
807
+ hasSelection = true;
806
808
  }
807
809
  }
808
-
810
+
811
+ if (!hasSelection) return;
812
+
813
+ // Add padding
814
+ const padding = 8;
815
+ minX -= padding;
816
+ minY -= padding;
817
+ maxX += padding;
818
+ maxY += padding;
819
+
820
+ const width = maxX - minX;
821
+ const height = maxY - minY;
822
+
823
+ // Create bounding box element
824
+ const box = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
825
+ box.setAttribute('x', minX);
826
+ box.setAttribute('y', minY);
827
+ box.setAttribute('width', width);
828
+ box.setAttribute('height', height);
829
+ box.setAttribute('fill', 'none');
830
+ box.setAttribute('stroke', '#007bff'); // Selection color
831
+ box.setAttribute('stroke-width', '1.5');
832
+ box.setAttribute('stroke-dasharray', '6, 4'); // Dotted/Dashed
833
+ box.setAttribute('stroke-opacity', '0.6'); // Light
834
+ box.setAttribute('rx', '8'); // Rounded corners
835
+ box.setAttribute('ry', '8');
836
+ box.style.pointerEvents = 'none';
837
+ box.classList.add('selection-bounds');
838
+
839
+ selectionLayer.appendChild(box);
809
840
  }
810
841
 
811
842
  /**
@@ -925,6 +956,66 @@ export class SelectTool extends Tool {
925
956
  });
926
957
  }
927
958
 
959
+ /**
960
+ * Separates selected segments into new strokes so they can be moved independently.
961
+ * @private
962
+ */
963
+ _separateSelectedParts() {
964
+ const newSelection = new Map();
965
+ const strokesToProcess = Array.from(this.selectedSegments.entries());
966
+
967
+ for (const [strokeId, selectedIndices] of strokesToProcess) {
968
+ const stroke = this.canvas.strokes.get(strokeId);
969
+ if (!stroke || !stroke.points || stroke.points.length < 2) continue;
970
+
971
+ const totalSegments = stroke.points.length - 1;
972
+
973
+ // If fully selected, just keep it as is
974
+ if (selectedIndices.size === totalSegments) {
975
+ newSelection.set(strokeId, selectedIndices);
976
+ continue;
977
+ }
978
+
979
+ // It's a partial selection - we need to split
980
+ const sortedSelectedIndices = Array.from(selectedIndices).sort((a, b) => a - b);
981
+
982
+ // Determine unselected indices
983
+ const unselectedIndices = [];
984
+ for (let i = 0; i < totalSegments; i++) {
985
+ if (!selectedIndices.has(i)) {
986
+ unselectedIndices.push(i);
987
+ }
988
+ }
989
+
990
+ // Group segments
991
+ const selectedGroups = this._groupConsecutiveSegments(sortedSelectedIndices);
992
+ const unselectedGroups = this._groupConsecutiveSegments(unselectedIndices);
993
+
994
+ // Create new strokes for selected parts
995
+ selectedGroups.forEach(group => {
996
+ const newStroke = this._createStrokeFromSegments(stroke, group);
997
+ if (newStroke) {
998
+ // Add to new selection (all segments selected)
999
+ const newIndices = new Set();
1000
+ for (let i = 0; i < newStroke.points.length - 1; i++) {
1001
+ newIndices.add(i);
1002
+ }
1003
+ newSelection.set(newStroke.id, newIndices);
1004
+ }
1005
+ });
1006
+
1007
+ // Create new strokes for unselected parts (don't add to selection)
1008
+ unselectedGroups.forEach(group => {
1009
+ this._createStrokeFromSegments(stroke, group);
1010
+ });
1011
+
1012
+ // Remove original stroke
1013
+ this.canvas.removeStroke(strokeId);
1014
+ }
1015
+
1016
+ this.selectedSegments = newSelection;
1017
+ }
1018
+
928
1019
  /**
929
1020
  * Groups consecutive segment indices into separate arrays.
930
1021
  * @private
@@ -958,9 +1049,10 @@ export class SelectTool extends Tool {
958
1049
  /**
959
1050
  * Creates a new stroke from a group of segment indices.
960
1051
  * @private
1052
+ * @returns {Stroke|null} The newly created stroke
961
1053
  */
962
1054
  _createStrokeFromSegments(originalStroke, segmentIndices) {
963
- if (segmentIndices.length === 0) return;
1055
+ if (segmentIndices.length === 0) return null;
964
1056
 
965
1057
  // Collect points for the new stroke
966
1058
  const newPoints = [];
@@ -974,7 +1066,7 @@ export class SelectTool extends Tool {
974
1066
  }
975
1067
 
976
1068
  // Only create stroke if we have at least 2 points
977
- if (newPoints.length < 2) return;
1069
+ if (newPoints.length < 2) return null;
978
1070
 
979
1071
  // Create new stroke with same properties
980
1072
  const newStroke = new Stroke({
@@ -991,6 +1083,7 @@ export class SelectTool extends Tool {
991
1083
 
992
1084
  newStroke.finish();
993
1085
  this.canvas.addStroke(newStroke);
1086
+ return newStroke;
994
1087
  }
995
1088
 
996
1089
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",