@teachinglab/omd 0.6.1 → 0.6.3

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 (198) hide show
  1. package/README.md +257 -251
  2. package/README.old.md +137 -137
  3. package/canvas/core/canvasConfig.js +202 -202
  4. package/canvas/drawing/segment.js +167 -167
  5. package/canvas/drawing/stroke.js +385 -385
  6. package/canvas/events/eventManager.js +444 -444
  7. package/canvas/events/pointerEventHandler.js +262 -262
  8. package/canvas/index.js +48 -48
  9. package/canvas/tools/PointerTool.js +71 -71
  10. package/canvas/tools/tool.js +222 -222
  11. package/canvas/utils/boundingBox.js +377 -377
  12. package/canvas/utils/mathUtils.js +258 -258
  13. package/docs/api/configuration-options.md +198 -198
  14. package/docs/api/eventManager.md +82 -82
  15. package/docs/api/focusFrameManager.md +144 -144
  16. package/docs/api/index.md +105 -105
  17. package/docs/api/main.md +62 -62
  18. package/docs/api/omdBinaryExpressionNode.md +86 -86
  19. package/docs/api/omdCanvas.md +83 -83
  20. package/docs/api/omdConfigManager.md +112 -112
  21. package/docs/api/omdConstantNode.md +52 -52
  22. package/docs/api/omdDisplay.md +87 -87
  23. package/docs/api/omdEquationNode.md +174 -174
  24. package/docs/api/omdEquationSequenceNode.md +258 -258
  25. package/docs/api/omdEquationStack.md +192 -192
  26. package/docs/api/omdFunctionNode.md +82 -82
  27. package/docs/api/omdGroupNode.md +78 -78
  28. package/docs/api/omdHelpers.md +87 -87
  29. package/docs/api/omdLeafNode.md +85 -85
  30. package/docs/api/omdNode.md +201 -201
  31. package/docs/api/omdOperationDisplayNode.md +117 -117
  32. package/docs/api/omdOperatorNode.md +91 -91
  33. package/docs/api/omdParenthesisNode.md +133 -133
  34. package/docs/api/omdPopup.md +191 -191
  35. package/docs/api/omdPowerNode.md +131 -131
  36. package/docs/api/omdRationalNode.md +144 -144
  37. package/docs/api/omdSequenceNode.md +128 -128
  38. package/docs/api/omdSimplification.md +78 -78
  39. package/docs/api/omdSqrtNode.md +144 -144
  40. package/docs/api/omdStepVisualizer.md +146 -146
  41. package/docs/api/omdStepVisualizerHighlighting.md +65 -65
  42. package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
  43. package/docs/api/omdStepVisualizerLayout.md +70 -70
  44. package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
  45. package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
  46. package/docs/api/omdToolbar.md +130 -130
  47. package/docs/api/omdTranscriptionService.md +95 -95
  48. package/docs/api/omdTreeDiff.md +169 -169
  49. package/docs/api/omdUnaryExpressionNode.md +137 -137
  50. package/docs/api/omdUtilities.md +82 -82
  51. package/docs/api/omdVariableNode.md +123 -123
  52. package/docs/api/selectTool.md +74 -74
  53. package/docs/api/simplificationEngine.md +97 -97
  54. package/docs/api/simplificationRules.md +76 -76
  55. package/docs/api/simplificationUtils.md +64 -64
  56. package/docs/api/transcribe.md +43 -43
  57. package/docs/api-reference.md +85 -85
  58. package/docs/index.html +453 -453
  59. package/docs/index.md +38 -38
  60. package/docs/omd-objects.md +258 -258
  61. package/index.js +79 -79
  62. package/jsvg/index.js +3 -0
  63. package/jsvg/jsvg.js +898 -898
  64. package/jsvg/jsvgComponents.js +357 -358
  65. package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
  66. package/npm-docs/README.md +251 -251
  67. package/npm-docs/api/api-reference.md +85 -85
  68. package/npm-docs/api/configuration-options.md +198 -198
  69. package/npm-docs/api/eventManager.md +82 -82
  70. package/npm-docs/api/expression-nodes.md +561 -561
  71. package/npm-docs/api/focusFrameManager.md +144 -144
  72. package/npm-docs/api/index.md +105 -105
  73. package/npm-docs/api/main.md +62 -62
  74. package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
  75. package/npm-docs/api/omdCanvas.md +83 -83
  76. package/npm-docs/api/omdConfigManager.md +112 -112
  77. package/npm-docs/api/omdConstantNode.md +52 -52
  78. package/npm-docs/api/omdDisplay.md +87 -87
  79. package/npm-docs/api/omdEquationNode.md +174 -174
  80. package/npm-docs/api/omdEquationSequenceNode.md +258 -258
  81. package/npm-docs/api/omdEquationStack.md +192 -192
  82. package/npm-docs/api/omdFunctionNode.md +82 -82
  83. package/npm-docs/api/omdGroupNode.md +78 -78
  84. package/npm-docs/api/omdHelpers.md +87 -87
  85. package/npm-docs/api/omdLeafNode.md +85 -85
  86. package/npm-docs/api/omdNode.md +201 -201
  87. package/npm-docs/api/omdOperationDisplayNode.md +117 -117
  88. package/npm-docs/api/omdOperatorNode.md +91 -91
  89. package/npm-docs/api/omdParenthesisNode.md +133 -133
  90. package/npm-docs/api/omdPopup.md +191 -191
  91. package/npm-docs/api/omdPowerNode.md +131 -131
  92. package/npm-docs/api/omdRationalNode.md +144 -144
  93. package/npm-docs/api/omdSequenceNode.md +128 -128
  94. package/npm-docs/api/omdSimplification.md +78 -78
  95. package/npm-docs/api/omdSqrtNode.md +144 -144
  96. package/npm-docs/api/omdStepVisualizer.md +146 -146
  97. package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
  98. package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
  99. package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
  100. package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
  101. package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
  102. package/npm-docs/api/omdToolbar.md +130 -130
  103. package/npm-docs/api/omdTranscriptionService.md +95 -95
  104. package/npm-docs/api/omdTreeDiff.md +169 -169
  105. package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
  106. package/npm-docs/api/omdUtilities.md +82 -82
  107. package/npm-docs/api/omdVariableNode.md +123 -123
  108. package/npm-docs/api/selectTool.md +74 -74
  109. package/npm-docs/api/simplificationEngine.md +97 -97
  110. package/npm-docs/api/simplificationRules.md +76 -76
  111. package/npm-docs/api/simplificationUtils.md +64 -64
  112. package/npm-docs/api/transcribe.md +43 -43
  113. package/npm-docs/guides/equations.md +854 -854
  114. package/npm-docs/guides/factory-functions.md +354 -354
  115. package/npm-docs/guides/getting-started.md +318 -318
  116. package/npm-docs/guides/quick-examples.md +525 -525
  117. package/npm-docs/guides/visualizations.md +682 -682
  118. package/npm-docs/index.html +12 -0
  119. package/npm-docs/json-schemas.md +826 -826
  120. package/omd/config/omdConfigManager.js +279 -267
  121. package/omd/core/index.js +158 -158
  122. package/omd/core/omdEquationStack.js +606 -547
  123. package/omd/core/omdUtilities.js +113 -113
  124. package/omd/display/omdDisplay.js +1045 -963
  125. package/omd/display/omdToolbar.js +501 -501
  126. package/omd/nodes/omdBinaryExpressionNode.js +459 -459
  127. package/omd/nodes/omdConstantNode.js +141 -141
  128. package/omd/nodes/omdEquationNode.js +1327 -1327
  129. package/omd/nodes/omdFunctionNode.js +351 -351
  130. package/omd/nodes/omdGroupNode.js +67 -67
  131. package/omd/nodes/omdLeafNode.js +76 -76
  132. package/omd/nodes/omdNode.js +556 -556
  133. package/omd/nodes/omdOperationDisplayNode.js +321 -321
  134. package/omd/nodes/omdOperatorNode.js +108 -108
  135. package/omd/nodes/omdParenthesisNode.js +292 -292
  136. package/omd/nodes/omdPowerNode.js +235 -235
  137. package/omd/nodes/omdRationalNode.js +295 -295
  138. package/omd/nodes/omdSqrtNode.js +307 -307
  139. package/omd/nodes/omdUnaryExpressionNode.js +227 -227
  140. package/omd/nodes/omdVariableNode.js +122 -122
  141. package/omd/simplification/omdSimplification.js +140 -140
  142. package/omd/simplification/omdSimplificationEngine.js +887 -887
  143. package/omd/simplification/package.json +5 -5
  144. package/omd/simplification/rules/binaryRules.js +1037 -1037
  145. package/omd/simplification/rules/functionRules.js +111 -111
  146. package/omd/simplification/rules/index.js +48 -48
  147. package/omd/simplification/rules/parenthesisRules.js +19 -19
  148. package/omd/simplification/rules/powerRules.js +143 -143
  149. package/omd/simplification/rules/rationalRules.js +725 -725
  150. package/omd/simplification/rules/sqrtRules.js +48 -48
  151. package/omd/simplification/rules/unaryRules.js +37 -37
  152. package/omd/simplification/simplificationRules.js +31 -31
  153. package/omd/simplification/simplificationUtils.js +1055 -1055
  154. package/omd/step-visualizer/omdStepVisualizer.js +947 -947
  155. package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
  156. package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
  157. package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
  158. package/omd/utils/aiNextEquationStep.js +106 -106
  159. package/omd/utils/omdNodeOverlay.js +638 -638
  160. package/omd/utils/omdPopup.js +1203 -1203
  161. package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
  162. package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
  163. package/omd/utils/omdTranscriptionService.js +123 -123
  164. package/omd/utils/omdTreeDiff.js +733 -733
  165. package/package.json +59 -57
  166. package/readme.html +184 -120
  167. package/src/index.js +74 -74
  168. package/src/json-schemas.md +576 -576
  169. package/src/omd-json-samples.js +147 -147
  170. package/src/omdApp.js +391 -391
  171. package/src/omdAppCanvas.js +335 -335
  172. package/src/omdBalanceHanger.js +199 -199
  173. package/src/omdColor.js +13 -13
  174. package/src/omdCoordinatePlane.js +541 -541
  175. package/src/omdExpression.js +115 -115
  176. package/src/omdFactory.js +150 -150
  177. package/src/omdFunction.js +114 -114
  178. package/src/omdMetaExpression.js +290 -290
  179. package/src/omdNaturalExpression.js +563 -563
  180. package/src/omdNode.js +383 -383
  181. package/src/omdNumber.js +52 -52
  182. package/src/omdNumberLine.js +114 -112
  183. package/src/omdNumberTile.js +118 -118
  184. package/src/omdOperator.js +72 -72
  185. package/src/omdPowerExpression.js +91 -91
  186. package/src/omdProblem.js +259 -259
  187. package/src/omdRatioChart.js +251 -251
  188. package/src/omdRationalExpression.js +114 -114
  189. package/src/omdSampleData.js +215 -215
  190. package/src/omdShapes.js +512 -512
  191. package/src/omdSpinner.js +151 -151
  192. package/src/omdString.js +49 -49
  193. package/src/omdTable.js +498 -498
  194. package/src/omdTapeDiagram.js +244 -244
  195. package/src/omdTerm.js +91 -91
  196. package/src/omdTileEquation.js +349 -349
  197. package/src/omdUtils.js +84 -84
  198. package/src/omdVariable.js +51 -51
@@ -1,308 +1,308 @@
1
- import { omdNode } from "./omdNode.js";
2
- import { getNodeForAST } from "../core/omdUtilities.js";
3
- import { omdConstantNode } from "./omdConstantNode.js";
4
- import { omdPowerNode } from "./omdPowerNode.js";
5
- import { omdBinaryExpressionNode } from "./omdBinaryExpressionNode.js";
6
- import { simplifyStep } from "../simplification/omdSimplification.js";
7
- import { jsvgPath, jsvgLine } from '@teachinglab/jsvg';
8
- /**
9
- * Represents a square root node in the mathematical expression tree
10
- * Handles rendering of radical symbol and expression under the root
11
- * @extends omdNode
12
- */
13
- export class omdSqrtNode extends omdNode {
14
- /**
15
- * Creates a square root node from AST data
16
- * @param {Object} astNodeData - The AST node containing sqrt function information
17
- */
18
- constructor(astNodeData) {
19
- super(astNodeData);
20
- this.type = "omdSqrtNode";
21
- this.args = astNodeData.args || [];
22
-
23
- this.value = this.parseValue();
24
- this.argument = this.createArgumentNode();
25
-
26
- // Populate the argumentNodeList for the mathematical child node
27
- if (this.argument) {
28
- this.argumentNodeList.argument = this.argument;
29
- }
30
-
31
- [ this.radicalPath, this.radicalLine ] = this.createRadicalElements();
32
- }
33
-
34
- parseValue() {
35
- return "sqrt";
36
- }
37
-
38
- /**
39
- * Creates node for the expression under the radical
40
- * @private
41
- */
42
- createArgumentNode() {
43
- if (this.args.length === 0) return null;
44
-
45
- const argAst = this.args[0];
46
- const ArgNodeType = getNodeForAST(argAst);
47
- let child = new ArgNodeType(argAst);
48
- this.addChild(child);
49
-
50
- return child;
51
- }
52
-
53
- /**
54
- * Creates radical symbol and line for sqrt
55
- * @private
56
- */
57
- createRadicalElements() {
58
- // Create custom radical symbol using SVG path
59
- let radicalPath = new jsvgPath();
60
- radicalPath.setStrokeColor('black');
61
- radicalPath.setStrokeWidth(2);
62
- radicalPath.setFillColor('none');
63
- this.addChild(radicalPath);
64
-
65
- // Create the horizontal line over the expression
66
- let radicalLine = new jsvgLine();
67
- radicalLine.setStrokeColor('black');
68
- radicalLine.setStrokeWidth(2);
69
- this.addChild(radicalLine);
70
-
71
- return [radicalPath, radicalLine];
72
- }
73
-
74
- /**
75
- * Calculates the dimensions of the sqrt node and its children
76
- * @override
77
- */
78
- computeDimensions() {
79
- if (!this.argument) return;
80
-
81
- const fontSize = this.getFontSize();
82
- const argFontSize = fontSize * 5/6; // Match rational node scaling
83
-
84
- // Set font size for argument and compute its dimensions
85
- this.argument.setFontSize(argFontSize);
86
- this.argument.computeDimensions();
87
-
88
- // Calculate dimensions using the expression height to size the radical
89
- const ratio = fontSize / this.getRootFontSize();
90
- const spacing = 4 * ratio;
91
-
92
- const argWidth = this.argument.width;
93
- const argHeight = this.argument.height;
94
-
95
- // Radical width is proportional to expression height
96
- const radicalWidth = Math.max(12 * ratio, argHeight * 0.3);
97
-
98
- const totalWidth = radicalWidth + spacing + argWidth + spacing;
99
- const totalHeight = argHeight + 8 * ratio; // Extra height for the radical top
100
-
101
- this.setWidthAndHeight(totalWidth, totalHeight);
102
- }
103
-
104
- /**
105
- * Updates the layout of the sqrt node and its children
106
- * @override
107
- */
108
- updateLayout() {
109
- if (!this.argument) return;
110
-
111
- const fontSize = this.getFontSize();
112
- const ratio = fontSize / this.getRootFontSize();
113
- const spacing = 4 * ratio;
114
-
115
- let currentX = 0;
116
-
117
- // Calculate radical dimensions based on expression
118
- const expressionHeight = this.argument.height;
119
- const radicalWidth = Math.max(12 * ratio, expressionHeight * 0.3);
120
-
121
- // Position the expression first to get its exact position
122
- const expressionX = currentX + radicalWidth + spacing;
123
- const expressionY = (this.height - expressionHeight) / 2;
124
-
125
- this.argument.setPosition(expressionX, expressionY);
126
- this.argument.updateLayout();
127
-
128
- // Draw the radical path using addPoint method
129
- const radicalBottom = expressionY + expressionHeight - 2 * ratio;
130
- const radicalTop = expressionY - 4 * ratio;
131
- const radicalMid = expressionY + expressionHeight * 0.7;
132
-
133
- // Clear previous points and create radical path: short diagonal down, long diagonal up
134
- this.radicalPath.clearPoints();
135
- this.radicalPath.addPoint(currentX + radicalWidth * 0.3, radicalMid);
136
- this.radicalPath.addPoint(currentX + radicalWidth * 0.6, radicalBottom);
137
- this.radicalPath.addPoint(currentX + radicalWidth, radicalTop);
138
- this.radicalPath.updatePath();
139
-
140
- // Position horizontal line above the expression
141
- const lineY = expressionY - 2 * ratio;
142
- const lineStartX = currentX + radicalWidth;
143
- const lineEndX = expressionX + this.argument.width + spacing / 2;
144
- this.radicalLine.setEndpoints(lineStartX, lineY, lineEndX, lineY);
145
- }
146
-
147
- clone() {
148
- let newAstData;
149
- if (typeof this.astNodeData.clone === 'function') {
150
- newAstData = this.astNodeData.clone();
151
- } else {
152
- newAstData = JSON.parse(JSON.stringify(this.astNodeData));
153
- }
154
- const clone = new omdSqrtNode(newAstData);
155
-
156
- // Keep the backRect from the clone, not from 'this'
157
- const backRect = clone.backRect;
158
- clone.removeAllChildren();
159
- clone.addChild(backRect);
160
-
161
- // Create new jsvg elements for the clone
162
- clone.radicalPath = new jsvgPath();
163
- clone.radicalPath.setStrokeColor('black');
164
- clone.radicalPath.setStrokeWidth(2);
165
- clone.radicalPath.setFillColor('none');
166
- clone.addChild(clone.radicalPath);
167
-
168
- clone.radicalLine = new jsvgLine();
169
- clone.radicalLine.setStrokeColor('black');
170
- clone.radicalLine.setStrokeWidth(2);
171
- clone.addChild(clone.radicalLine);
172
-
173
- if (this.argument) {
174
- clone.argument = this.argument.clone();
175
- clone.addChild(clone.argument);
176
-
177
- // Explicitly update the argumentNodeList in the cloned node
178
- clone.argumentNodeList.argument = clone.argument;
179
-
180
- // The crucial step: link the clone to its origin
181
- clone.provenance.push(this.id);
182
- }
183
-
184
- return clone;
185
- }
186
-
187
- /**
188
- * Highlights the sqrt node and its argument
189
- */
190
- highlightAll() {
191
- this.select();
192
-
193
- if (this.argument && this.argument.highlightAll) {
194
- this.argument.highlightAll();
195
- }
196
- }
197
-
198
- /**
199
- * Unhighlights the sqrt node and its argument
200
- */
201
- unhighlightAll() {
202
- this.deselect();
203
-
204
- if (this.argument && this.argument.unhighlightAll) {
205
- this.argument.unhighlightAll();
206
- }
207
- }
208
-
209
- /**
210
- * Converts the omdSqrtNode to a math.js AST node.
211
- * @returns {Object} A math.js-compatible AST node.
212
- */
213
- toMathJSNode() {
214
- const astNode = {
215
- type: 'FunctionNode',
216
- fn: { type: 'SymbolNode', name: 'sqrt', clone: function() { return {...this}; } },
217
- args: this.argument ? [this.argument.toMathJSNode()] : []
218
- };
219
-
220
- // Add a clone method to maintain compatibility with math.js's expectations.
221
- astNode.clone = function() {
222
- const clonedNode = { ...this };
223
- if (this.args) {
224
- clonedNode.args = this.args.map(arg => arg.clone());
225
- }
226
- if (this.fn && typeof this.fn.clone === 'function') {
227
- clonedNode.fn = this.fn.clone();
228
- }
229
- return clonedNode;
230
- };
231
- return astNode;
232
- }
233
- /**
234
- * Converts the square root node to a string representation.
235
- * @returns {string} The string representation.
236
- */
237
- toString() {
238
- return `sqrt(${this.argument ? this.argument.toString() : ''})`;
239
- }
240
-
241
- /**
242
- * Evaluate the root expression.
243
- * @param {Object} variables - Variable name to value mapping
244
- * @returns {number} The evaluated root
245
- */
246
- evaluate(variables = {}) {
247
- if (!this.argument || !this.argument.evaluate) {
248
- return NaN;
249
- }
250
- const radicandValue = this.argument.evaluate(variables);
251
- if (radicandValue < 0) {
252
- return NaN; // Or handle complex numbers if desired
253
- }
254
- return Math.sqrt(radicandValue);
255
- }
256
-
257
- /**
258
- * Check if this is a square root (index = 2).
259
- * @returns {boolean}
260
- */
261
- isSquareRoot() {
262
- return true;
263
- }
264
-
265
- /**
266
- * Check if this is a cube root (index = 3).
267
- * @returns {boolean}
268
- */
269
- isCubeRoot() {
270
- return false;
271
- }
272
-
273
- /**
274
- * Convert to equivalent power notation.
275
- * @returns {omdPowerNode} Equivalent power expression
276
- */
277
- toPowerForm() {
278
- if (!this.argument) return null;
279
-
280
- const powerAst = {
281
- type: 'OperatorNode', op: '^', fn: 'pow',
282
- args: [
283
- this.argument.toMathJSNode(),
284
- omdConstantNode.fromNumber(0.5).toMathJSNode()
285
- ]
286
- };
287
- return new omdPowerNode(powerAst);
288
- }
289
-
290
- /**
291
- * Create a root node from a string.
292
- * @static
293
- * @param {string} expressionString - Expression with root
294
- * @returns {omdSqrtNode}
295
- */
296
- static fromString(expressionString) {
297
- try {
298
- const ast = window.math.parse(expressionString);
299
- if (ast.type === 'FunctionNode' && ast.fn.name === 'sqrt') {
300
- return new omdSqrtNode(ast);
301
- }
302
- throw new Error("Expression is not a 'sqrt' function.");
303
- } catch (error) {
304
- console.error("Failed to create sqrt node from string:", error);
305
- throw error;
306
- }
307
- }
1
+ import { omdNode } from "./omdNode.js";
2
+ import { getNodeForAST } from "../core/omdUtilities.js";
3
+ import { omdConstantNode } from "./omdConstantNode.js";
4
+ import { omdPowerNode } from "./omdPowerNode.js";
5
+ import { omdBinaryExpressionNode } from "./omdBinaryExpressionNode.js";
6
+ import { simplifyStep } from "../simplification/omdSimplification.js";
7
+ import { jsvgPath, jsvgLine } from '@teachinglab/jsvg';
8
+ /**
9
+ * Represents a square root node in the mathematical expression tree
10
+ * Handles rendering of radical symbol and expression under the root
11
+ * @extends omdNode
12
+ */
13
+ export class omdSqrtNode extends omdNode {
14
+ /**
15
+ * Creates a square root node from AST data
16
+ * @param {Object} astNodeData - The AST node containing sqrt function information
17
+ */
18
+ constructor(astNodeData) {
19
+ super(astNodeData);
20
+ this.type = "omdSqrtNode";
21
+ this.args = astNodeData.args || [];
22
+
23
+ this.value = this.parseValue();
24
+ this.argument = this.createArgumentNode();
25
+
26
+ // Populate the argumentNodeList for the mathematical child node
27
+ if (this.argument) {
28
+ this.argumentNodeList.argument = this.argument;
29
+ }
30
+
31
+ [ this.radicalPath, this.radicalLine ] = this.createRadicalElements();
32
+ }
33
+
34
+ parseValue() {
35
+ return "sqrt";
36
+ }
37
+
38
+ /**
39
+ * Creates node for the expression under the radical
40
+ * @private
41
+ */
42
+ createArgumentNode() {
43
+ if (this.args.length === 0) return null;
44
+
45
+ const argAst = this.args[0];
46
+ const ArgNodeType = getNodeForAST(argAst);
47
+ let child = new ArgNodeType(argAst);
48
+ this.addChild(child);
49
+
50
+ return child;
51
+ }
52
+
53
+ /**
54
+ * Creates radical symbol and line for sqrt
55
+ * @private
56
+ */
57
+ createRadicalElements() {
58
+ // Create custom radical symbol using SVG path
59
+ let radicalPath = new jsvgPath();
60
+ radicalPath.setStrokeColor('black');
61
+ radicalPath.setStrokeWidth(2);
62
+ radicalPath.setFillColor('none');
63
+ this.addChild(radicalPath);
64
+
65
+ // Create the horizontal line over the expression
66
+ let radicalLine = new jsvgLine();
67
+ radicalLine.setStrokeColor('black');
68
+ radicalLine.setStrokeWidth(2);
69
+ this.addChild(radicalLine);
70
+
71
+ return [radicalPath, radicalLine];
72
+ }
73
+
74
+ /**
75
+ * Calculates the dimensions of the sqrt node and its children
76
+ * @override
77
+ */
78
+ computeDimensions() {
79
+ if (!this.argument) return;
80
+
81
+ const fontSize = this.getFontSize();
82
+ const argFontSize = fontSize * 5/6; // Match rational node scaling
83
+
84
+ // Set font size for argument and compute its dimensions
85
+ this.argument.setFontSize(argFontSize);
86
+ this.argument.computeDimensions();
87
+
88
+ // Calculate dimensions using the expression height to size the radical
89
+ const ratio = fontSize / this.getRootFontSize();
90
+ const spacing = 4 * ratio;
91
+
92
+ const argWidth = this.argument.width;
93
+ const argHeight = this.argument.height;
94
+
95
+ // Radical width is proportional to expression height
96
+ const radicalWidth = Math.max(12 * ratio, argHeight * 0.3);
97
+
98
+ const totalWidth = radicalWidth + spacing + argWidth + spacing;
99
+ const totalHeight = argHeight + 8 * ratio; // Extra height for the radical top
100
+
101
+ this.setWidthAndHeight(totalWidth, totalHeight);
102
+ }
103
+
104
+ /**
105
+ * Updates the layout of the sqrt node and its children
106
+ * @override
107
+ */
108
+ updateLayout() {
109
+ if (!this.argument) return;
110
+
111
+ const fontSize = this.getFontSize();
112
+ const ratio = fontSize / this.getRootFontSize();
113
+ const spacing = 4 * ratio;
114
+
115
+ let currentX = 0;
116
+
117
+ // Calculate radical dimensions based on expression
118
+ const expressionHeight = this.argument.height;
119
+ const radicalWidth = Math.max(12 * ratio, expressionHeight * 0.3);
120
+
121
+ // Position the expression first to get its exact position
122
+ const expressionX = currentX + radicalWidth + spacing;
123
+ const expressionY = (this.height - expressionHeight) / 2;
124
+
125
+ this.argument.setPosition(expressionX, expressionY);
126
+ this.argument.updateLayout();
127
+
128
+ // Draw the radical path using addPoint method
129
+ const radicalBottom = expressionY + expressionHeight - 2 * ratio;
130
+ const radicalTop = expressionY - 4 * ratio;
131
+ const radicalMid = expressionY + expressionHeight * 0.7;
132
+
133
+ // Clear previous points and create radical path: short diagonal down, long diagonal up
134
+ this.radicalPath.clearPoints();
135
+ this.radicalPath.addPoint(currentX + radicalWidth * 0.3, radicalMid);
136
+ this.radicalPath.addPoint(currentX + radicalWidth * 0.6, radicalBottom);
137
+ this.radicalPath.addPoint(currentX + radicalWidth, radicalTop);
138
+ this.radicalPath.updatePath();
139
+
140
+ // Position horizontal line above the expression
141
+ const lineY = expressionY - 2 * ratio;
142
+ const lineStartX = currentX + radicalWidth;
143
+ const lineEndX = expressionX + this.argument.width + spacing / 2;
144
+ this.radicalLine.setEndpoints(lineStartX, lineY, lineEndX, lineY);
145
+ }
146
+
147
+ clone() {
148
+ let newAstData;
149
+ if (typeof this.astNodeData.clone === 'function') {
150
+ newAstData = this.astNodeData.clone();
151
+ } else {
152
+ newAstData = JSON.parse(JSON.stringify(this.astNodeData));
153
+ }
154
+ const clone = new omdSqrtNode(newAstData);
155
+
156
+ // Keep the backRect from the clone, not from 'this'
157
+ const backRect = clone.backRect;
158
+ clone.removeAllChildren();
159
+ clone.addChild(backRect);
160
+
161
+ // Create new jsvg elements for the clone
162
+ clone.radicalPath = new jsvgPath();
163
+ clone.radicalPath.setStrokeColor('black');
164
+ clone.radicalPath.setStrokeWidth(2);
165
+ clone.radicalPath.setFillColor('none');
166
+ clone.addChild(clone.radicalPath);
167
+
168
+ clone.radicalLine = new jsvgLine();
169
+ clone.radicalLine.setStrokeColor('black');
170
+ clone.radicalLine.setStrokeWidth(2);
171
+ clone.addChild(clone.radicalLine);
172
+
173
+ if (this.argument) {
174
+ clone.argument = this.argument.clone();
175
+ clone.addChild(clone.argument);
176
+
177
+ // Explicitly update the argumentNodeList in the cloned node
178
+ clone.argumentNodeList.argument = clone.argument;
179
+
180
+ // The crucial step: link the clone to its origin
181
+ clone.provenance.push(this.id);
182
+ }
183
+
184
+ return clone;
185
+ }
186
+
187
+ /**
188
+ * Highlights the sqrt node and its argument
189
+ */
190
+ highlightAll() {
191
+ this.select();
192
+
193
+ if (this.argument && this.argument.highlightAll) {
194
+ this.argument.highlightAll();
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Unhighlights the sqrt node and its argument
200
+ */
201
+ unhighlightAll() {
202
+ this.deselect();
203
+
204
+ if (this.argument && this.argument.unhighlightAll) {
205
+ this.argument.unhighlightAll();
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Converts the omdSqrtNode to a math.js AST node.
211
+ * @returns {Object} A math.js-compatible AST node.
212
+ */
213
+ toMathJSNode() {
214
+ const astNode = {
215
+ type: 'FunctionNode',
216
+ fn: { type: 'SymbolNode', name: 'sqrt', clone: function() { return {...this}; } },
217
+ args: this.argument ? [this.argument.toMathJSNode()] : []
218
+ };
219
+
220
+ // Add a clone method to maintain compatibility with math.js's expectations.
221
+ astNode.clone = function() {
222
+ const clonedNode = { ...this };
223
+ if (this.args) {
224
+ clonedNode.args = this.args.map(arg => arg.clone());
225
+ }
226
+ if (this.fn && typeof this.fn.clone === 'function') {
227
+ clonedNode.fn = this.fn.clone();
228
+ }
229
+ return clonedNode;
230
+ };
231
+ return astNode;
232
+ }
233
+ /**
234
+ * Converts the square root node to a string representation.
235
+ * @returns {string} The string representation.
236
+ */
237
+ toString() {
238
+ return `sqrt(${this.argument ? this.argument.toString() : ''})`;
239
+ }
240
+
241
+ /**
242
+ * Evaluate the root expression.
243
+ * @param {Object} variables - Variable name to value mapping
244
+ * @returns {number} The evaluated root
245
+ */
246
+ evaluate(variables = {}) {
247
+ if (!this.argument || !this.argument.evaluate) {
248
+ return NaN;
249
+ }
250
+ const radicandValue = this.argument.evaluate(variables);
251
+ if (radicandValue < 0) {
252
+ return NaN; // Or handle complex numbers if desired
253
+ }
254
+ return Math.sqrt(radicandValue);
255
+ }
256
+
257
+ /**
258
+ * Check if this is a square root (index = 2).
259
+ * @returns {boolean}
260
+ */
261
+ isSquareRoot() {
262
+ return true;
263
+ }
264
+
265
+ /**
266
+ * Check if this is a cube root (index = 3).
267
+ * @returns {boolean}
268
+ */
269
+ isCubeRoot() {
270
+ return false;
271
+ }
272
+
273
+ /**
274
+ * Convert to equivalent power notation.
275
+ * @returns {omdPowerNode} Equivalent power expression
276
+ */
277
+ toPowerForm() {
278
+ if (!this.argument) return null;
279
+
280
+ const powerAst = {
281
+ type: 'OperatorNode', op: '^', fn: 'pow',
282
+ args: [
283
+ this.argument.toMathJSNode(),
284
+ omdConstantNode.fromNumber(0.5).toMathJSNode()
285
+ ]
286
+ };
287
+ return new omdPowerNode(powerAst);
288
+ }
289
+
290
+ /**
291
+ * Create a root node from a string.
292
+ * @static
293
+ * @param {string} expressionString - Expression with root
294
+ * @returns {omdSqrtNode}
295
+ */
296
+ static fromString(expressionString) {
297
+ try {
298
+ const ast = window.math.parse(expressionString);
299
+ if (ast.type === 'FunctionNode' && ast.fn.name === 'sqrt') {
300
+ return new omdSqrtNode(ast);
301
+ }
302
+ throw new Error("Expression is not a 'sqrt' function.");
303
+ } catch (error) {
304
+ console.error("Failed to create sqrt node from string:", error);
305
+ throw error;
306
+ }
307
+ }
308
308
  }