@teachinglab/omd 0.7.9 → 0.7.11

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.
@@ -445,13 +445,64 @@ export class ResizeHandleManager {
445
445
  if (!this.selectedElement) return { x: 0, y: 0, width: 0, height: 0 };
446
446
 
447
447
  try {
448
- // Get bounding box of the content inside the wrapper
448
+ // Get the content inside the wrapper (usually an SVG element)
449
449
  const content = this.selectedElement.firstElementChild;
450
- if (content) {
451
- return content.getBBox();
452
- } else {
450
+ if (!content) {
453
451
  return this.selectedElement.getBBox();
454
452
  }
453
+
454
+ // For elements with clip paths (like coordinate planes),
455
+ // the graph lines extend beyond the visible area causing getBBox() to be too large.
456
+ // We need to find the clip rect to get the actual visible bounds.
457
+ const clipPaths = content.querySelectorAll('clipPath');
458
+
459
+ if (clipPaths.length > 0) {
460
+ let maxArea = 0;
461
+ let clipBounds = null;
462
+
463
+ for (const clipPath of clipPaths) {
464
+ const rect = clipPath.querySelector('rect');
465
+ if (rect) {
466
+ const w = parseFloat(rect.getAttribute('width')) || 0;
467
+ const h = parseFloat(rect.getAttribute('height')) || 0;
468
+ const x = parseFloat(rect.getAttribute('x')) || 0;
469
+ const y = parseFloat(rect.getAttribute('y')) || 0;
470
+
471
+ const area = w * h;
472
+ if (area > maxArea) {
473
+ maxArea = area;
474
+
475
+ // Find the transform on the content group
476
+ const contentGroup = content.firstElementChild;
477
+ let tx = 0, ty = 0;
478
+ if (contentGroup) {
479
+ const transform = contentGroup.getAttribute('transform');
480
+ if (transform) {
481
+ const translateMatch = transform.match(/translate\(\s*([^,]+)(?:,\s*([^)]+))?\s*\)/);
482
+ if (translateMatch) {
483
+ tx = parseFloat(translateMatch[1]) || 0;
484
+ ty = parseFloat(translateMatch[2]) || 0;
485
+ }
486
+ }
487
+ }
488
+
489
+ clipBounds = {
490
+ x: x + tx,
491
+ y: y + ty,
492
+ width: w,
493
+ height: h
494
+ };
495
+ }
496
+ }
497
+ }
498
+
499
+ if (clipBounds) {
500
+ return clipBounds;
501
+ }
502
+ }
503
+
504
+ // Fallback to getBBox if no clip path found
505
+ return content.getBBox();
455
506
  } catch (error) {
456
507
  // Fallback if getBBox fails
457
508
  return { x: 0, y: 0, width: 100, height: 100 };
@@ -663,11 +663,11 @@ export class SelectTool extends Tool {
663
663
 
664
664
  /**
665
665
  * Gets the bounds of an OMD element including transform
666
+ * Uses clip paths for accurate bounds when present (e.g., coordinate planes)
666
667
  * @private
667
668
  */
668
669
  _getOMDElementBounds(item) {
669
670
  try {
670
- const bbox = item.getBBox();
671
671
  const transform = item.getAttribute('transform') || '';
672
672
  let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
673
673
 
@@ -683,6 +683,68 @@ export class SelectTool extends Tool {
683
683
  scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
684
684
  }
685
685
 
686
+ // Get the content element (usually an SVG inside the wrapper)
687
+ const content = item.firstElementChild;
688
+ let bbox;
689
+
690
+ if (content) {
691
+ // Check for clip paths to get accurate visible bounds
692
+ // This is important for coordinate planes where graph lines extend beyond visible area
693
+ const clipPaths = content.querySelectorAll('clipPath');
694
+
695
+ if (clipPaths.length > 0) {
696
+ let maxArea = 0;
697
+ let clipBounds = null;
698
+
699
+ for (const clipPath of clipPaths) {
700
+ const rect = clipPath.querySelector('rect');
701
+ if (rect) {
702
+ const w = parseFloat(rect.getAttribute('width')) || 0;
703
+ const h = parseFloat(rect.getAttribute('height')) || 0;
704
+ const x = parseFloat(rect.getAttribute('x')) || 0;
705
+ const y = parseFloat(rect.getAttribute('y')) || 0;
706
+
707
+ const area = w * h;
708
+ if (area > maxArea) {
709
+ maxArea = area;
710
+
711
+ // Find the transform on the content group
712
+ const contentGroup = content.firstElementChild;
713
+ let tx = 0, ty = 0;
714
+ if (contentGroup) {
715
+ const contentTransform = contentGroup.getAttribute('transform');
716
+ if (contentTransform) {
717
+ const contentTranslateMatch = contentTransform.match(/translate\(\s*([^,]+)(?:,\s*([^)]+))?\s*\)/);
718
+ if (contentTranslateMatch) {
719
+ tx = parseFloat(contentTranslateMatch[1]) || 0;
720
+ ty = parseFloat(contentTranslateMatch[2]) || 0;
721
+ }
722
+ }
723
+ }
724
+
725
+ clipBounds = {
726
+ x: x + tx,
727
+ y: y + ty,
728
+ width: w,
729
+ height: h
730
+ };
731
+ }
732
+ }
733
+ }
734
+
735
+ if (clipBounds) {
736
+ bbox = clipBounds;
737
+ }
738
+ }
739
+
740
+ // Fallback to getBBox if no clip path found
741
+ if (!bbox) {
742
+ bbox = content.getBBox();
743
+ }
744
+ } else {
745
+ bbox = item.getBBox();
746
+ }
747
+
686
748
  return {
687
749
  x: offsetX + (bbox.x * scaleX),
688
750
  y: offsetY + (bbox.y * scaleY),
@@ -738,42 +800,20 @@ export class SelectTool extends Tool {
738
800
  continue;
739
801
  }
740
802
  try {
741
- // Get the bounding box of the item
742
- const bbox = item.getBBox();
743
-
744
- // Parse transform to get the actual position
745
- const transform = item.getAttribute('transform') || '';
746
- let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
747
-
748
- // Parse translate
749
- const translateMatch = transform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
750
- if (translateMatch) {
751
- offsetX = parseFloat(translateMatch[1]) || 0;
752
- offsetY = parseFloat(translateMatch[2]) || 0;
753
- }
754
-
755
- // Parse scale
756
- const scaleMatch = transform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
757
- if (scaleMatch) {
758
- scaleX = parseFloat(scaleMatch[1]) || 1;
759
- scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
760
- }
761
-
762
- // Calculate the actual bounds including transform
763
- const actualX = offsetX + (bbox.x * scaleX);
764
- const actualY = offsetY + (bbox.y * scaleY);
765
- const actualWidth = bbox.width * scaleX;
766
- const actualHeight = bbox.height * scaleY;
767
-
768
- // Add some padding for easier clicking
769
- const padding = 10;
803
+ // Use the shared bounds calculation method (handles clip paths correctly)
804
+ const itemBounds = this._getOMDElementBounds(item);
770
805
 
771
- // Check if point is within the bounds (with padding)
772
- if (x >= actualX - padding &&
773
- x <= actualX + actualWidth + padding &&
774
- y >= actualY - padding &&
775
- y <= actualY + actualHeight + padding) {
776
- return item;
806
+ if (itemBounds) {
807
+ // Add some padding for easier clicking
808
+ const padding = 10;
809
+
810
+ // Check if point is within the bounds (with padding)
811
+ if (x >= itemBounds.x - padding &&
812
+ x <= itemBounds.x + itemBounds.width + padding &&
813
+ y >= itemBounds.y - padding &&
814
+ y <= itemBounds.y + itemBounds.height + padding) {
815
+ return item;
816
+ }
777
817
  }
778
818
  } catch (error) {
779
819
  // Skip items that can't be measured (continue with next)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",