@teachinglab/omd 0.1.0

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.
Files changed (144) hide show
  1. package/README.md +138 -0
  2. package/canvas/core/canvasConfig.js +203 -0
  3. package/canvas/core/omdCanvas.js +475 -0
  4. package/canvas/drawing/segment.js +168 -0
  5. package/canvas/drawing/stroke.js +386 -0
  6. package/canvas/events/eventManager.js +435 -0
  7. package/canvas/events/pointerEventHandler.js +263 -0
  8. package/canvas/features/focusFrameManager.js +287 -0
  9. package/canvas/index.js +49 -0
  10. package/canvas/tools/eraserTool.js +322 -0
  11. package/canvas/tools/pencilTool.js +319 -0
  12. package/canvas/tools/selectTool.js +457 -0
  13. package/canvas/tools/tool.js +223 -0
  14. package/canvas/tools/toolManager.js +394 -0
  15. package/canvas/ui/cursor.js +438 -0
  16. package/canvas/ui/toolbar.js +304 -0
  17. package/canvas/utils/boundingBox.js +378 -0
  18. package/canvas/utils/mathUtils.js +259 -0
  19. package/docs/api/configuration-options.md +104 -0
  20. package/docs/api/eventManager.md +68 -0
  21. package/docs/api/focusFrameManager.md +150 -0
  22. package/docs/api/index.md +91 -0
  23. package/docs/api/main.md +58 -0
  24. package/docs/api/omdBinaryExpressionNode.md +227 -0
  25. package/docs/api/omdCanvas.md +142 -0
  26. package/docs/api/omdConfigManager.md +192 -0
  27. package/docs/api/omdConstantNode.md +117 -0
  28. package/docs/api/omdDisplay.md +121 -0
  29. package/docs/api/omdEquationNode.md +161 -0
  30. package/docs/api/omdEquationSequenceNode.md +301 -0
  31. package/docs/api/omdEquationStack.md +139 -0
  32. package/docs/api/omdFunctionNode.md +141 -0
  33. package/docs/api/omdGroupNode.md +182 -0
  34. package/docs/api/omdHelpers.md +96 -0
  35. package/docs/api/omdLeafNode.md +163 -0
  36. package/docs/api/omdNode.md +101 -0
  37. package/docs/api/omdOperationDisplayNode.md +139 -0
  38. package/docs/api/omdOperatorNode.md +127 -0
  39. package/docs/api/omdParenthesisNode.md +122 -0
  40. package/docs/api/omdPopup.md +117 -0
  41. package/docs/api/omdPowerNode.md +127 -0
  42. package/docs/api/omdRationalNode.md +128 -0
  43. package/docs/api/omdSequenceNode.md +128 -0
  44. package/docs/api/omdSimplification.md +110 -0
  45. package/docs/api/omdSqrtNode.md +79 -0
  46. package/docs/api/omdStepVisualizer.md +115 -0
  47. package/docs/api/omdStepVisualizerHighlighting.md +61 -0
  48. package/docs/api/omdStepVisualizerInteractiveSteps.md +129 -0
  49. package/docs/api/omdStepVisualizerLayout.md +60 -0
  50. package/docs/api/omdStepVisualizerNodeUtils.md +140 -0
  51. package/docs/api/omdStepVisualizerTextBoxes.md +68 -0
  52. package/docs/api/omdToolbar.md +102 -0
  53. package/docs/api/omdTranscriptionService.md +76 -0
  54. package/docs/api/omdTreeDiff.md +134 -0
  55. package/docs/api/omdUnaryExpressionNode.md +174 -0
  56. package/docs/api/omdUtilities.md +70 -0
  57. package/docs/api/omdVariableNode.md +148 -0
  58. package/docs/api/selectTool.md +74 -0
  59. package/docs/api/simplificationEngine.md +98 -0
  60. package/docs/api/simplificationRules.md +77 -0
  61. package/docs/api/simplificationUtils.md +64 -0
  62. package/docs/api/transcribe.md +43 -0
  63. package/docs/api-reference.md +85 -0
  64. package/docs/index.html +454 -0
  65. package/docs/user-guide.md +9 -0
  66. package/index.js +67 -0
  67. package/omd/config/omdConfigManager.js +267 -0
  68. package/omd/core/index.js +150 -0
  69. package/omd/core/omdEquationStack.js +347 -0
  70. package/omd/core/omdUtilities.js +115 -0
  71. package/omd/display/omdDisplay.js +443 -0
  72. package/omd/display/omdToolbar.js +502 -0
  73. package/omd/nodes/omdBinaryExpressionNode.js +460 -0
  74. package/omd/nodes/omdConstantNode.js +142 -0
  75. package/omd/nodes/omdEquationNode.js +1223 -0
  76. package/omd/nodes/omdEquationSequenceNode.js +1273 -0
  77. package/omd/nodes/omdFunctionNode.js +352 -0
  78. package/omd/nodes/omdGroupNode.js +68 -0
  79. package/omd/nodes/omdLeafNode.js +77 -0
  80. package/omd/nodes/omdNode.js +557 -0
  81. package/omd/nodes/omdOperationDisplayNode.js +322 -0
  82. package/omd/nodes/omdOperatorNode.js +109 -0
  83. package/omd/nodes/omdParenthesisNode.js +293 -0
  84. package/omd/nodes/omdPowerNode.js +236 -0
  85. package/omd/nodes/omdRationalNode.js +295 -0
  86. package/omd/nodes/omdSqrtNode.js +308 -0
  87. package/omd/nodes/omdUnaryExpressionNode.js +178 -0
  88. package/omd/nodes/omdVariableNode.js +123 -0
  89. package/omd/simplification/omdSimplification.js +171 -0
  90. package/omd/simplification/omdSimplificationEngine.js +886 -0
  91. package/omd/simplification/package.json +6 -0
  92. package/omd/simplification/rules/binaryRules.js +1037 -0
  93. package/omd/simplification/rules/functionRules.js +111 -0
  94. package/omd/simplification/rules/index.js +48 -0
  95. package/omd/simplification/rules/parenthesisRules.js +19 -0
  96. package/omd/simplification/rules/powerRules.js +143 -0
  97. package/omd/simplification/rules/rationalRules.js +475 -0
  98. package/omd/simplification/rules/sqrtRules.js +48 -0
  99. package/omd/simplification/rules/unaryRules.js +37 -0
  100. package/omd/simplification/simplificationRules.js +32 -0
  101. package/omd/simplification/simplificationUtils.js +1056 -0
  102. package/omd/step-visualizer/omdStepVisualizer.js +597 -0
  103. package/omd/step-visualizer/omdStepVisualizerHighlighting.js +206 -0
  104. package/omd/step-visualizer/omdStepVisualizerLayout.js +245 -0
  105. package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +163 -0
  106. package/omd/utils/omdNodeOverlay.js +638 -0
  107. package/omd/utils/omdPopup.js +1084 -0
  108. package/omd/utils/omdStepVisualizerInteractiveSteps.js +491 -0
  109. package/omd/utils/omdStepVisualizerNodeUtils.js +268 -0
  110. package/omd/utils/omdTranscriptionService.js +125 -0
  111. package/omd/utils/omdTreeDiff.js +734 -0
  112. package/package.json +46 -0
  113. package/src/index.js +62 -0
  114. package/src/json-schemas.md +109 -0
  115. package/src/omd-json-samples.js +115 -0
  116. package/src/omd.js +109 -0
  117. package/src/omdApp.js +391 -0
  118. package/src/omdAppCanvas.js +336 -0
  119. package/src/omdBalanceHanger.js +172 -0
  120. package/src/omdColor.js +13 -0
  121. package/src/omdCoordinatePlane.js +467 -0
  122. package/src/omdEquation.js +125 -0
  123. package/src/omdExpression.js +104 -0
  124. package/src/omdFunction.js +113 -0
  125. package/src/omdMetaExpression.js +287 -0
  126. package/src/omdNaturalExpression.js +564 -0
  127. package/src/omdNode.js +384 -0
  128. package/src/omdNumber.js +53 -0
  129. package/src/omdNumberLine.js +107 -0
  130. package/src/omdNumberTile.js +119 -0
  131. package/src/omdOperator.js +73 -0
  132. package/src/omdPowerExpression.js +92 -0
  133. package/src/omdProblem.js +55 -0
  134. package/src/omdRatioChart.js +232 -0
  135. package/src/omdRationalExpression.js +115 -0
  136. package/src/omdSampleData.js +215 -0
  137. package/src/omdShapes.js +476 -0
  138. package/src/omdSpinner.js +148 -0
  139. package/src/omdString.js +39 -0
  140. package/src/omdTable.js +369 -0
  141. package/src/omdTapeDiagram.js +245 -0
  142. package/src/omdTerm.js +92 -0
  143. package/src/omdTileEquation.js +349 -0
  144. package/src/omdVariable.js +51 -0
@@ -0,0 +1,293 @@
1
+ import { omdGroupNode } from "./omdGroupNode.js";
2
+ import { omdNode } from "./omdNode.js";
3
+ import { getNodeForAST } from "../core/omdUtilities.js";
4
+
5
+ /**
6
+ * Represents a parenthesized expression node in the mathematical expression tree
7
+ * Handles rendering of parentheses and their contained expression
8
+ * @extends omdNode
9
+ */
10
+ export class omdParenthesisNode extends omdNode {
11
+ /**
12
+ * Creates a parenthesis node from AST data
13
+ * @param {Object} astNodeData - The AST node containing parenthesis information
14
+ */
15
+ constructor(astNodeData) {
16
+ super(astNodeData);
17
+ this.type = "omdParenthesisNode";
18
+
19
+ const innerContent = astNodeData.content || (astNodeData.args && astNodeData.args[0]);
20
+ if (!innerContent) {
21
+ console.error("omdParenthesisNode requires inner content", astNodeData);
22
+ return;
23
+ }
24
+
25
+ this.open = this.createParenthesis("(");
26
+ this.expression = this.createExpression(innerContent);
27
+ this.closed = this.createParenthesis(")");
28
+
29
+ // Populate the argumentNodeList for the mathematical child node
30
+ this.argumentNodeList.expression = this.expression;
31
+ }
32
+
33
+ createParenthesis(parenthesis) {
34
+ let child = new omdGroupNode(parenthesis);
35
+ this.addChild(child);
36
+
37
+ return child;
38
+ }
39
+
40
+ createExpression(ast) {
41
+ let ExpressionType = getNodeForAST(ast);
42
+ let child = new ExpressionType(ast);
43
+ this.addChild(child);
44
+
45
+ return child;
46
+ }
47
+
48
+ /**
49
+ * Calculates the dimensions of the parenthesis node and its children
50
+ * @override
51
+ */
52
+ computeDimensions() {
53
+ this.open.computeDimensions();
54
+ this.expression.computeDimensions();
55
+ this.closed.computeDimensions();
56
+
57
+ // Gerard: Calculate dimensions and padding
58
+ let padding = 4 * this.getFontSize() / this.getRootFontSize(); // Gerard: Padding should scale with font size
59
+ let sumWidth = this.open.width + this.expression.width + this.closed.width;
60
+ let maxHeight = Math.max(this.expression.height, this.closed.height, this.open.height);
61
+
62
+ this.setWidthAndHeight(sumWidth, maxHeight + padding);
63
+ }
64
+
65
+ /**
66
+ * Updates the layout of the parenthesis node and its children
67
+ * @override
68
+ */
69
+ updateLayout() {
70
+ let xCurrent = 0;
71
+
72
+ // For proper mathematical typesetting, parentheses should be centered
73
+ // relative to the baseline of the mathematical content, properly accounting
74
+ // for power nodes and their baseline positioning
75
+
76
+ // First, position the expression based on its alignment baseline
77
+ this.expression.updateLayout();
78
+ const expressionBaseline = this.expression.getAlignmentBaseline();
79
+ const totalBaseline = this.getAlignmentBaseline();
80
+ const expressionY = totalBaseline - expressionBaseline;
81
+ this.expression.setPosition(this.open.width, expressionY);
82
+
83
+ // Position parentheses centered around the mathematical baseline
84
+ // This ensures proper centering for power nodes where the baseline
85
+ // represents the position of the base (e.g., 'x' in 'x²')
86
+ const mathematicalBaseline = expressionY + expressionBaseline;
87
+ const parenY = mathematicalBaseline - (this.open.height / 2);
88
+
89
+ this.open.updateLayout();
90
+ this.open.setPosition(0, parenY);
91
+
92
+ this.closed.updateLayout();
93
+ this.closed.setPosition(this.open.width + this.expression.width, parenY);
94
+ }
95
+
96
+ /**
97
+ * For parenthesis nodes, the alignment baseline is the baseline of the inner expression.
98
+ * @override
99
+ * @returns {number} The y-coordinate for alignment.
100
+ */
101
+ getAlignmentBaseline() {
102
+ // The baseline is the y-position of the expression node plus its own baseline.
103
+ const childY = (this.height - this.expression.height) / 2;
104
+ return childY + this.expression.getAlignmentBaseline();
105
+ }
106
+
107
+ clone() {
108
+ // Create a new node. The constructor will add a backRect and temporary children.
109
+ const tempAst = { type: 'ParenthesisNode', content: {type: 'ConstantNode', value: 1} };
110
+ const clone = new omdParenthesisNode(tempAst);
111
+
112
+ // Keep the backRect, but get rid of the temporary children.
113
+ const backRect = clone.backRect;
114
+ clone.removeAllChildren();
115
+ clone.addChild(backRect);
116
+
117
+ // Manually clone the real children to ensure the entire tree has correct provenance.
118
+ clone.open = this.open.clone();
119
+ clone.addChild(clone.open);
120
+
121
+ clone.expression = this.expression.clone();
122
+ clone.addChild(clone.expression);
123
+
124
+ clone.closed = this.closed.clone();
125
+ clone.addChild(clone.closed);
126
+
127
+ // Rebuild the argument list
128
+ clone.argumentNodeList = { expression: clone.expression };
129
+
130
+ // Regenerate AST data from the cloned structure instead of copying it
131
+ // This ensures the AST properly reflects the cloned nodes
132
+ clone.astNodeData = clone.toMathJSNode();
133
+
134
+ // The crucial step: link the clone to its origin.
135
+ clone.provenance.push(this.id);
136
+
137
+ return clone;
138
+ }
139
+
140
+ /**
141
+ * A parenthesis node is constant if its inner expression is constant.
142
+ * @returns {boolean}
143
+ */
144
+ isConstant() {
145
+ return this.expression ? this.expression.isConstant() : false;
146
+ }
147
+
148
+ /**
149
+ * The value of a parenthesis node is the value of its inner expression.
150
+ * @returns {number}
151
+ */
152
+ getValue() {
153
+ if (!this.expression) {
154
+ throw new Error("Parenthesis node has no expression from which to get a value.");
155
+ }
156
+ return this.expression.getValue();
157
+ }
158
+
159
+ /**
160
+ * Converts the omdParenthesisNode to a math.js AST node.
161
+ * @returns {Object} A math.js-compatible AST node.
162
+ */
163
+ toMathJSNode() {
164
+ const astNode = {
165
+ type: 'ParenthesisNode',
166
+ content: this.expression.toMathJSNode(),
167
+ id: this.id,
168
+ provenance: this.provenance
169
+ };
170
+ // Add a clone method to maintain compatibility with math.js's expectations.
171
+ astNode.clone = function() {
172
+ const clonedNode = { ...this };
173
+ if (this.content) {
174
+ clonedNode.content = this.content.clone();
175
+ }
176
+ return clonedNode;
177
+ };
178
+ return astNode;
179
+ }
180
+
181
+ /**
182
+ * Converts the parenthesis node to a string.
183
+ * @returns {string} The string representation of the parenthesized expression.
184
+ */
185
+ toString() {
186
+ const innerExpr = this.expression.toString();
187
+ return `(${innerExpr})`;
188
+ }
189
+
190
+ /**
191
+ * Evaluate the content within parentheses
192
+ * @param {Object} variables - Variable name to value mapping
193
+ * @returns {number} The evaluated result
194
+ */
195
+ evaluate(variables = {}) {
196
+ if (!this.expression) {
197
+ throw new Error("Parenthesis node has no expression to evaluate");
198
+ }
199
+
200
+ // Evaluate the inner expression
201
+ if (this.expression.evaluate) {
202
+ return this.expression.evaluate(variables);
203
+ } else if (this.expression.isConstant()) {
204
+ return this.expression.getValue();
205
+ } else {
206
+ throw new Error("Cannot evaluate parenthesis content");
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Check if parentheses are necessary based on context
212
+ * @returns {boolean} Whether parentheses affect evaluation order
213
+ */
214
+ isNecessary() {
215
+ // If there's no parent, parentheses at the top level are not necessary
216
+ if (!this.parent) {
217
+ return false;
218
+ }
219
+
220
+ // Check for single constants or variables - parentheses not needed
221
+ if (this.expression.type === 'omdConstantNode' ||
222
+ this.expression.type === 'omdVariableNode') {
223
+ return false;
224
+ }
225
+
226
+ // Check parent context
227
+ const parent = this.parent;
228
+
229
+ // If parent is a function, parentheses are part of function syntax
230
+ if (parent.type === 'omdFunctionNode') {
231
+ return true;
232
+ }
233
+
234
+ // If parent is a power node and this is the base or exponent
235
+ if (parent.type === 'omdPowerNode') {
236
+ // Check if we're the base
237
+ if (parent.base === this) {
238
+ // Base needs parentheses if it's a complex expression
239
+ return this.expression.type === 'omdBinaryExpressionNode';
240
+ }
241
+ // Check if we're the exponent
242
+ if (parent.exponent === this) {
243
+ // Exponent needs parentheses if it's not a simple value
244
+ return !(this.expression.type === 'omdConstantNode' ||
245
+ this.expression.type === 'omdVariableNode');
246
+ }
247
+ }
248
+
249
+ // If parent is a binary expression, check operator precedence
250
+ if (parent.type === 'omdBinaryExpressionNode') {
251
+ // If the inner expression is also a binary expression,
252
+ // parentheses might be needed based on precedence
253
+ if (this.expression.type === 'omdBinaryExpressionNode') {
254
+ return true; // Conservative approach - keep parentheses
255
+ }
256
+ }
257
+
258
+ // Default: parentheses are not necessary
259
+ return false;
260
+ }
261
+
262
+ /**
263
+ * Create a parenthesis node from a string
264
+ * @param {string} expressionString - Expression with parentheses
265
+ * @returns {omdParenthesisNode} The created parenthesis node
266
+ * @static
267
+ */
268
+ static fromString(expressionString) {
269
+ if (!window.math) {
270
+ throw new Error("Math.js is required for parsing expressions");
271
+ }
272
+
273
+ const trimmed = expressionString.trim();
274
+
275
+ // Ensure the string starts and ends with parentheses
276
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
277
+ throw new Error("Expression must be enclosed in parentheses");
278
+ }
279
+
280
+ try {
281
+ const ast = window.math.parse(trimmed);
282
+
283
+ // Verify it's a parenthesis node
284
+ if (ast.type !== 'ParenthesisNode') {
285
+ throw new Error("Parsed expression is not a parenthesis node");
286
+ }
287
+
288
+ return new omdParenthesisNode(ast);
289
+ } catch (error) {
290
+ throw new Error(`Failed to parse parenthesis expression: ${error.message}`);
291
+ }
292
+ }
293
+ }
@@ -0,0 +1,236 @@
1
+ import { omdNode } from "./omdNode.js";
2
+ import { getNodeForAST } from "../core/omdUtilities.js";
3
+ import { omdConstantNode } from "./omdConstantNode.js";
4
+ import { omdBinaryExpressionNode } from "./omdBinaryExpressionNode.js";
5
+ import { simplifyStep } from "../simplification/omdSimplification.js";
6
+
7
+ /**
8
+ * Represents a power/exponentiation node in the mathematical expression tree
9
+ * Handles rendering of expressions with a base and an exponent
10
+ * @extends omdNode
11
+ */
12
+ export class omdPowerNode extends omdNode {
13
+ /**
14
+ * Creates a power node from AST data
15
+ * @param {Object} ast - The AST node containing power expression information
16
+ */
17
+ constructor(ast) {
18
+ super(ast);
19
+ this.type = "omdPowerNode";
20
+
21
+ // Validate that this is actually a power expression
22
+ if (!ast.args || ast.args.length !== 2) {
23
+ console.error("omdPowerNode requires an AST node with exactly 2 args (base and exponent)", ast);
24
+ return;
25
+ }
26
+
27
+ this.value = this.parseValue();
28
+ this.base = this.createOperand(ast.args[0]);
29
+ this.exponent = this.createOperand(ast.args[1]);
30
+
31
+ // Populate the argumentNodeList for mathematical child nodes
32
+ this.argumentNodeList.base = this.base;
33
+ this.argumentNodeList.exponent = this.exponent;
34
+ }
35
+
36
+ parseValue() {
37
+ return "^";
38
+ }
39
+
40
+ createOperand(ast) {
41
+ let OperandType = getNodeForAST(ast);
42
+ let child = new OperandType(ast);
43
+ this.addChild(child);
44
+
45
+ return child;
46
+ }
47
+
48
+ // Gerard - BUG: Sizing and layout of power nodes causes too much extra space in rational nodes.
49
+ // Find a solution that doesn't also mess up layout in binary expressions
50
+ computeDimensions() {
51
+ this.base.computeDimensions();
52
+ this.exponent.setFontSize(this.getFontSize() * 3 / 4);
53
+ this.exponent.computeDimensions();
54
+
55
+ const sumWidth = this.base.width + this.exponent.width;
56
+
57
+ // The total height must include the exponent to reserve the correct space within containers.
58
+ const totalHeight = this.base.height + this.getSuperscriptOffset();
59
+
60
+ this.setWidthAndHeight(sumWidth, totalHeight);
61
+ }
62
+
63
+ updateLayout() {
64
+ // Position the base at the bottom of the node's bounding box to ensure
65
+ // there's room for the exponent above it
66
+ const baseY = this.height - this.base.height;
67
+ this.base.updateLayout();
68
+ this.base.setPosition(0, baseY);
69
+
70
+ // Position the exponent above the base
71
+ const exponentY = baseY - this.getSuperscriptOffset();
72
+ this.exponent.updateLayout();
73
+ this.exponent.setPosition(this.base.width, exponentY);
74
+ }
75
+
76
+ /**
77
+ * Calculates the vertical offset for the exponent based on the current font size.
78
+ * @returns {number} The vertical offset in pixels.
79
+ */
80
+ getSuperscriptOffset() {
81
+ // This factor determines how high the exponent is lifted.
82
+ // It's a proportion of the main font size for consistent scaling.
83
+ return this.getFontSize() * 0.4;
84
+ }
85
+
86
+ /**
87
+ * For power nodes, the alignment baseline should match where the base's text baseline
88
+ * actually appears within the power node's coordinate system. This is more robust
89
+ * and works for complex bases (e.g. groups) as well as simple variables.
90
+ * @override
91
+ * @returns {number} The y-coordinate for alignment.
92
+ */
93
+ getAlignmentBaseline() {
94
+ // The base is positioned at a 'baseY' from the top of this node's bounding box.
95
+ // Its true alignment baseline is that offset plus its own internal baseline.
96
+ const baseY = this.height - this.base.height;
97
+ return baseY + this.base.getAlignmentBaseline();
98
+ }
99
+
100
+ clone() {
101
+ let newAstData;
102
+ if (typeof this.astNodeData.clone === 'function') {
103
+ newAstData = this.astNodeData.clone();
104
+ } else {
105
+ newAstData = JSON.parse(JSON.stringify(this.astNodeData));
106
+ }
107
+ const clone = new omdPowerNode(newAstData);
108
+
109
+ // Keep the backRect from the clone, not from 'this'
110
+ const backRect = clone.backRect;
111
+ clone.removeAllChildren();
112
+ clone.addChild(backRect);
113
+
114
+ clone.base = this.base.clone();
115
+ clone.exponent = this.exponent.clone();
116
+
117
+ clone.addChild(clone.base);
118
+ clone.addChild(clone.exponent);
119
+
120
+ // Explicitly update the argumentNodeList in the cloned node
121
+ clone.argumentNodeList.base = clone.base;
122
+ clone.argumentNodeList.exponent = clone.exponent;
123
+
124
+ // The crucial step: link the clone to its origin
125
+ clone.provenance.push(this.id);
126
+
127
+ return clone;
128
+ }
129
+
130
+ /**
131
+ * Converts the omdPowerNode to a math.js AST node.
132
+ * @returns {Object} A math.js-compatible AST node.
133
+ */
134
+ toMathJSNode() {
135
+ const astNode = {
136
+ type: 'OperatorNode', op: '^', fn: 'pow',
137
+ args: [this.base.toMathJSNode(), this.exponent.toMathJSNode()]
138
+ };
139
+ // Add a clone method to maintain compatibility with math.js's expectations.
140
+ astNode.clone = function() {
141
+ const clonedNode = { ...this };
142
+ clonedNode.args = this.args.map(arg => arg.clone());
143
+ return clonedNode;
144
+ };
145
+ return astNode;
146
+ }
147
+
148
+ /**
149
+ * Returns a string representation of the power node.
150
+ * @returns {string}
151
+ */
152
+ toString() {
153
+ const baseExpr = this.base.toString();
154
+ const expExpr = this.exponent.toString();
155
+
156
+ // Add parentheses to base if it's a binary expression that needs them
157
+ let baseStr = baseExpr;
158
+ if (this.base.needsParentheses && this.base.needsParentheses()) {
159
+ baseStr = `(${baseExpr})`;
160
+ } else if (this.base.type === 'BinaryExpressionNode' ||
161
+ (this.base.constructor && this.base.type === 'omdBinaryExpressionNode')) {
162
+ // Binary expressions in base always need parentheses
163
+ baseStr = `(${baseExpr})`;
164
+ }
165
+
166
+ // Add parentheses to exponent if it's complex
167
+ let expStr = expExpr;
168
+ if (this.exponent.type === 'BinaryExpressionNode' ||
169
+ (this.exponent.constructor && this.exponent.type === 'omdBinaryExpressionNode') ||
170
+ (this.exponent.constructor && this.exponent.type === 'omdPowerNode')) {
171
+ expStr = `(${expExpr})`;
172
+ }
173
+
174
+ return `${baseStr}^${expStr}`;
175
+ }
176
+
177
+ /**
178
+ * Evaluate the power expression
179
+ * @param {Object} variables - Variable name to value mapping
180
+ * @returns {number} The result of base^exponent
181
+ */
182
+ evaluate(variables = {}) {
183
+ const baseValue = this.base.evaluate ? this.base.evaluate(variables) :
184
+ (this.base.value !== undefined ? parseFloat(this.base.value) : NaN);
185
+ const expValue = this.exponent.evaluate ? this.exponent.evaluate(variables) :
186
+ (this.exponent.value !== undefined ? parseFloat(this.exponent.value) : NaN);
187
+
188
+ if (isNaN(baseValue) || isNaN(expValue)) {
189
+ return NaN;
190
+ }
191
+
192
+ return Math.pow(baseValue, expValue);
193
+ }
194
+
195
+ /**
196
+ * Check if this is a square (exponent = 2)
197
+ * @returns {boolean}
198
+ */
199
+ isSquare() {
200
+ return this.exponent.value === 2 ||
201
+ (this.exponent.constructor && this.exponent.type === 'omdConstantNode' &&
202
+ parseFloat(this.exponent.value) === 2);
203
+ }
204
+
205
+ /**
206
+ * Check if this is a cube (exponent = 3)
207
+ * @returns {boolean}
208
+ */
209
+ isCube() {
210
+ return this.exponent.value === 3 ||
211
+ (this.exponent.constructor && this.exponent.type === 'omdConstantNode' &&
212
+ parseFloat(this.exponent.value) === 3);
213
+ }
214
+
215
+ /**
216
+ * Create a power node from a string
217
+ * @static
218
+ * @param {string} expressionString - Expression with exponentiation
219
+ * @returns {omdPowerNode}
220
+ */
221
+ static fromString(expressionString) {
222
+ try {
223
+ const ast = window.math.parse(expressionString);
224
+
225
+ // Check if it's actually a power expression
226
+ if (ast.type !== 'OperatorNode' || ast.op !== '^') {
227
+ throw new Error("Expression is not a power operation");
228
+ }
229
+
230
+ return new omdPowerNode(ast);
231
+ } catch (error) {
232
+ console.error("Failed to create power node from string:", error);
233
+ throw error;
234
+ }
235
+ }
236
+ }