@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.
- package/canvas/tools/PointerTool.js +52 -36
- package/package.json +1 -1
- package/src/omdExpression.js +61 -0
|
@@ -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
|
-
//
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
y
|
|
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
package/src/omdExpression.js
CHANGED
|
@@ -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;
|