@teachinglab/omd 0.7.19 → 0.7.21

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.
@@ -669,22 +669,61 @@ export class PointerTool extends Tool {
669
669
  */
670
670
  _getOMDElementBounds(item) {
671
671
  try {
672
- const bbox = item.getBBox();
673
672
  const transform = item.getAttribute('transform') || '';
674
673
  let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
675
-
674
+
676
675
  const translateMatch = transform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
677
676
  if (translateMatch) {
678
677
  offsetX = parseFloat(translateMatch[1]) || 0;
679
678
  offsetY = parseFloat(translateMatch[2]) || 0;
680
679
  }
681
-
680
+
682
681
  const scaleMatch = transform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
683
682
  if (scaleMatch) {
684
683
  scaleX = parseFloat(scaleMatch[1]) || 1;
685
684
  scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
686
685
  }
687
-
686
+
687
+ // Use clip-rect logic (same as ResizeHandleManager) to avoid
688
+ // getBBox() returning the full unclipped geometry for coordinate planes.
689
+ const content = item.firstElementChild;
690
+ let bbox = null;
691
+
692
+ if (content) {
693
+ const clipPaths = content.querySelectorAll('clipPath');
694
+ if (clipPaths.length > 0) {
695
+ let maxArea = 0;
696
+ for (const clipPath of clipPaths) {
697
+ const rect = clipPath.querySelector('rect');
698
+ if (rect) {
699
+ const w = parseFloat(rect.getAttribute('width')) || 0;
700
+ const h = parseFloat(rect.getAttribute('height')) || 0;
701
+ const x = parseFloat(rect.getAttribute('x')) || 0;
702
+ const y = parseFloat(rect.getAttribute('y')) || 0;
703
+ if (w * h > maxArea) {
704
+ maxArea = w * h;
705
+ // Account for the content group's own translate
706
+ const contentGroup = content.firstElementChild;
707
+ let tx = 0, ty = 0;
708
+ if (contentGroup) {
709
+ const t = contentGroup.getAttribute('transform');
710
+ if (t) {
711
+ const m = t.match(/translate\(\s*([^,]+)(?:,\s*([^)]+))?\s*\)/);
712
+ if (m) { tx = parseFloat(m[1]) || 0; ty = parseFloat(m[2]) || 0; }
713
+ }
714
+ }
715
+ bbox = { x: x + tx, y: y + ty, width: w, height: h };
716
+ }
717
+ }
718
+ }
719
+ }
720
+ if (!bbox) {
721
+ bbox = content.getBBox();
722
+ }
723
+ } else {
724
+ bbox = item.getBBox();
725
+ }
726
+
688
727
  return {
689
728
  x: offsetX + (bbox.x * scaleX),
690
729
  y: offsetY + (bbox.y * scaleY),
@@ -740,41 +779,18 @@ export class PointerTool extends Tool {
740
779
  continue;
741
780
  }
742
781
  try {
743
- // Get the bounding box of the item
744
- const bbox = item.getBBox();
745
-
746
- // Parse transform to get the actual position
747
- const transform = item.getAttribute('transform') || '';
748
- let offsetX = 0, offsetY = 0, scaleX = 1, scaleY = 1;
749
-
750
- // Parse translate
751
- const translateMatch = transform.match(/translate\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/);
752
- if (translateMatch) {
753
- offsetX = parseFloat(translateMatch[1]) || 0;
754
- offsetY = parseFloat(translateMatch[2]) || 0;
755
- }
756
-
757
- // Parse scale
758
- const scaleMatch = transform.match(/scale\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)/);
759
- if (scaleMatch) {
760
- scaleX = parseFloat(scaleMatch[1]) || 1;
761
- scaleY = scaleMatch[2] ? parseFloat(scaleMatch[2]) : scaleX;
762
- }
763
-
764
- // Calculate the actual bounds including transform
765
- const actualX = offsetX + (bbox.x * scaleX);
766
- const actualY = offsetY + (bbox.y * scaleY);
767
- const actualWidth = bbox.width * scaleX;
768
- const actualHeight = bbox.height * scaleY;
769
-
782
+ // Reuse _getOMDElementBounds which applies clip-rect logic
783
+ // to avoid oversized hit areas on coordinate planes.
784
+ const bounds = this._getOMDElementBounds(item);
785
+ if (!bounds) continue;
786
+
770
787
  // Add some padding for easier clicking
771
788
  const padding = 10;
772
789
 
773
- // Check if point is within the bounds (with padding)
774
- if (x >= actualX - padding &&
775
- x <= actualX + actualWidth + padding &&
776
- y >= actualY - padding &&
777
- y <= actualY + actualHeight + padding) {
790
+ if (x >= bounds.x - padding &&
791
+ x <= bounds.x + bounds.width + padding &&
792
+ y >= bounds.y - padding &&
793
+ y <= bounds.y + bounds.height + padding) {
778
794
  return item;
779
795
  }
780
796
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.7.19",
3
+ "version": "0.7.21",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -8,6 +8,7 @@ import { omdNumber } from "./omdNumber.js";
8
8
  import { omdVariable } from "./omdVariable.js";
9
9
  import { omdMetaExpression } from "./omdMetaExpression.js"
10
10
  import { parseExpressionString } from "./omdUtils.js";
11
+ import { getNodeForAST } from "../omd/core/omdUtilities.js";
11
12
 
12
13
  export class omdExpression extends omdMetaExpression
13
14
  {
@@ -21,6 +22,7 @@ export class omdExpression extends omdMetaExpression
21
22
  this.operatorSet = [];
22
23
 
23
24
  this.inset = 5;
25
+ this.expressionNode = null;
24
26
 
25
27
  this.expressionStack = new jsvgLayoutGroup();
26
28
  this.expressionStack.setPosition( this.inset, 0 );
@@ -28,15 +30,56 @@ export class omdExpression extends omdMetaExpression
28
30
  this.addChild( this.expressionStack );
29
31
  }
30
32
 
33
+ _renderWithExpressionNode(expressionString, fontSize) {
34
+ if (typeof math === 'undefined' || typeof math.parse !== 'function') {
35
+ throw new Error('math.js is required to parse expression strings');
36
+ }
37
+
38
+ const ast = math.parse(expressionString);
39
+ const NodeType = getNodeForAST(ast);
40
+ const exprNode = new NodeType(ast);
41
+
42
+ if (typeof fontSize === 'number' && exprNode.setFontSize) {
43
+ exprNode.setFontSize(fontSize);
44
+ }
45
+
46
+ if (exprNode.hideBackgroundByDefault) exprNode.hideBackgroundByDefault();
47
+ if (exprNode.computeDimensions) exprNode.computeDimensions();
48
+ if (exprNode.updateLayout) exprNode.updateLayout();
49
+
50
+ if (typeof this.expressionStack.removeAllChildren === 'function') {
51
+ this.expressionStack.removeAllChildren();
52
+ } else {
53
+ this.expressionStack.childList = [];
54
+ }
55
+ this.expressionStack.addChild(exprNode);
56
+ this.expressionStack.setSpacer(0);
57
+
58
+ this.expressionNode = exprNode;
59
+ this.updateLayout();
60
+ }
61
+
31
62
  loadFromJSON( data )
32
63
  {
33
64
  // Accept either structured object or a plain expression string
34
65
  if ( typeof data === 'string' ) {
66
+ try {
67
+ this._renderWithExpressionNode(data, data.fontSize);
68
+ return;
69
+ } catch (e) {
70
+ console.warn('⚠️ omdExpression math.js render failed, falling back to legacy parsing:', e?.message || e);
71
+ }
35
72
  const parsed = parseExpressionString(data);
36
73
  if ( parsed ) data = parsed;
37
74
  }
38
75
  // Handle object with 'expression' string property
39
76
  else if ( typeof data === 'object' && data.expression && typeof data.expression === 'string' ) {
77
+ try {
78
+ this._renderWithExpressionNode(data.expression, data.fontSize);
79
+ return;
80
+ } catch (e) {
81
+ console.warn('⚠️ omdExpression math.js render failed, falling back to legacy parsing:', e?.message || e);
82
+ }
40
83
  const parsed = parseExpressionString(data.expression);
41
84
  if ( parsed ) {
42
85
  // Merge parsed data into data object
@@ -117,6 +160,24 @@ export class omdExpression extends omdMetaExpression
117
160
 
118
161
  updateLayout()
119
162
  {
163
+ if (this.expressionNode) {
164
+ const node = this.expressionNode;
165
+ if (node.computeDimensions) node.computeDimensions();
166
+ if (node.updateLayout) node.updateLayout();
167
+
168
+ this.expressionStack.doHorizontalLayout();
169
+ this.expressionStack.setPosition(this.inset, this.inset);
170
+
171
+ const W = node.width || this.expressionStack.width;
172
+ const H = node.height || this.expressionStack.height;
173
+
174
+ this.backRect.setWidthAndHeight( W + this.inset*2, H + this.inset*2 );
175
+ this.setWidthAndHeight( this.backRect.width, this.backRect.height );
176
+ this.width = this.backRect.width;
177
+ this.height = this.backRect.height;
178
+ return;
179
+ }
180
+
120
181
  this.expressionStack.doHorizontalLayout();
121
182
 
122
183
  var W = this.expressionStack.width;