@teachinglab/omd 0.3.0 → 0.3.2

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 (57) hide show
  1. package/docs/api/configuration-options.md +198 -198
  2. package/docs/api/eventManager.md +82 -82
  3. package/docs/api/focusFrameManager.md +144 -144
  4. package/docs/api/index.md +105 -105
  5. package/docs/api/main.md +62 -62
  6. package/docs/api/omdBinaryExpressionNode.md +86 -86
  7. package/docs/api/omdCanvas.md +83 -83
  8. package/docs/api/omdConfigManager.md +112 -112
  9. package/docs/api/omdConstantNode.md +52 -52
  10. package/docs/api/omdDisplay.md +87 -87
  11. package/docs/api/omdEquationNode.md +174 -174
  12. package/docs/api/omdEquationSequenceNode.md +258 -258
  13. package/docs/api/omdEquationStack.md +156 -156
  14. package/docs/api/omdFunctionNode.md +82 -82
  15. package/docs/api/omdGroupNode.md +78 -78
  16. package/docs/api/omdHelpers.md +87 -87
  17. package/docs/api/omdLeafNode.md +85 -85
  18. package/docs/api/omdNode.md +201 -201
  19. package/docs/api/omdOperationDisplayNode.md +117 -117
  20. package/docs/api/omdOperatorNode.md +91 -91
  21. package/docs/api/omdParenthesisNode.md +133 -133
  22. package/docs/api/omdPopup.md +191 -191
  23. package/docs/api/omdPowerNode.md +131 -131
  24. package/docs/api/omdRationalNode.md +144 -144
  25. package/docs/api/omdSimplification.md +78 -78
  26. package/docs/api/omdSqrtNode.md +144 -144
  27. package/docs/api/omdStepVisualizer.md +146 -146
  28. package/docs/api/omdStepVisualizerHighlighting.md +65 -65
  29. package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
  30. package/docs/api/omdStepVisualizerLayout.md +70 -70
  31. package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
  32. package/docs/api/omdTranscriptionService.md +95 -95
  33. package/docs/api/omdTreeDiff.md +169 -169
  34. package/docs/api/omdUnaryExpressionNode.md +137 -137
  35. package/docs/api/omdUtilities.md +82 -82
  36. package/docs/api/omdVariableNode.md +123 -123
  37. package/omd/nodes/omdConstantNode.js +141 -141
  38. package/omd/nodes/omdGroupNode.js +67 -67
  39. package/omd/nodes/omdLeafNode.js +76 -76
  40. package/omd/nodes/omdOperatorNode.js +108 -108
  41. package/omd/nodes/omdParenthesisNode.js +292 -292
  42. package/omd/nodes/omdPowerNode.js +235 -235
  43. package/omd/nodes/omdRationalNode.js +295 -295
  44. package/omd/nodes/omdVariableNode.js +122 -122
  45. package/omd/simplification/omdSimplification.js +140 -140
  46. package/omd/step-visualizer/omdStepVisualizer.js +947 -947
  47. package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
  48. package/package.json +2 -1
  49. package/src/index.js +11 -0
  50. package/src/omdBalanceHanger.js +2 -1
  51. package/src/omdEquation.js +1 -1
  52. package/src/omdNumber.js +1 -1
  53. package/src/omdNumberLine.js +13 -7
  54. package/src/omdRatioChart.js +19 -0
  55. package/src/omdShapes.js +1 -1
  56. package/src/omdTapeDiagram.js +1 -1
  57. package/src/omdTerm.js +1 -1
@@ -1,293 +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
- }
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
293
  }