@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,322 @@
1
+ import { omdNode } from "./omdNode.js";
2
+ import { omdVariableNode } from "./omdVariableNode.js";
3
+ import { getNodeForAST } from "../core/omdUtilities.js";
4
+ import { getMultiplicationSymbol } from "../config/omdConfigManager.js";
5
+
6
+ /**
7
+ * Represents a visual node for displaying an operation applied to both sides of an equation.
8
+ * Shows both operations with appropriate spacing between them.
9
+ * Example: -3 -3
10
+ */
11
+ export class omdOperationDisplayNode extends omdNode {
12
+ static OPERATOR_SYMBOLS = {
13
+ 'add': '+',
14
+ 'subtract': '-',
15
+ 'multiply': '×',
16
+ 'divide': '÷'
17
+ };
18
+
19
+ /**
20
+ * Creates an operation display node.
21
+ * @param {string} operation - The type of operation (e.g., 'add', 'subtract', 'multiply', 'divide').
22
+ * @param {number|string|omdNode} value - The value being applied. Can be a number, variable name, or an omdNode.
23
+ */
24
+ constructor(operation, value) {
25
+ super({});
26
+ this.operation = operation;
27
+ this.value = value;
28
+ this.type = "omdOperationDisplayNode";
29
+
30
+ this._initializeDisplay();
31
+ this._createOperationElements();
32
+ this._disableAllInteractions();
33
+ this._addChildElements();
34
+ }
35
+
36
+ /**
37
+ * @private
38
+ */
39
+ _initializeDisplay() {
40
+ this.hideBackgroundByDefault();
41
+ if (this.backRect) {
42
+ this.backRect.setOpacity(0);
43
+ this.backRect.setFillColor("transparent");
44
+ }
45
+ // Ensure this node never highlights
46
+ this._makeNodeNonHighlightable();
47
+ }
48
+
49
+ /**
50
+ * @private
51
+ */
52
+ _createOperationElements() {
53
+ const symbol = this._getOperatorSymbol(this.operation);
54
+ // Render each side as a single node token: "(op+value)" e.g., "(-5)"
55
+ const tokenText = `${symbol}${this._valueToString(this.value)}`;
56
+ this.leftToken = new omdVariableNode(tokenText);
57
+ this.rightToken = new omdVariableNode(tokenText);
58
+ // Ensure tokens are fully initialized and sized
59
+ if (typeof this.leftToken.initialize === 'function') this.leftToken.initialize();
60
+ if (typeof this.rightToken.initialize === 'function') this.rightToken.initialize();
61
+ // Propagate font size if available
62
+ const fs = (typeof this.getFontSize === 'function') ? this.getFontSize() : null;
63
+ if (fs && typeof this.leftToken.setFontSize === 'function') this.leftToken.setFontSize(fs);
64
+ if (fs && typeof this.rightToken.setFontSize === 'function') this.rightToken.setFontSize(fs);
65
+
66
+ // Immediately disable highlighting for created elements
67
+ [this.leftToken, this.rightToken].forEach(element => {
68
+ this._disableHighlighting(element);
69
+ });
70
+ }
71
+
72
+ /**
73
+ * @private
74
+ */
75
+ _disableAllInteractions() {
76
+ [this.leftToken, this.rightToken].forEach(element => {
77
+ this._disableElement(element);
78
+ });
79
+ }
80
+
81
+ /**
82
+ * @private
83
+ */
84
+ _addChildElements() {
85
+ this.addChild(this.leftToken);
86
+ this.addChild(this.rightToken);
87
+ }
88
+
89
+ /**
90
+ * Converts the operation string to its display symbol.
91
+ * @param {string} operation
92
+ * @returns {string} The symbol.
93
+ * @private
94
+ */
95
+ _getOperatorSymbol(operation) {
96
+ return omdOperationDisplayNode.OPERATOR_SYMBOLS[operation] || '';
97
+ }
98
+
99
+ /**
100
+ * Creates an appropriate omdNode for the value.
101
+ * @param {number|string|omdNode} value
102
+ * @returns {omdNode}
103
+ * @private
104
+ */
105
+ _createValueElement(value) {
106
+ // Not used in combined-token approach, retained for compatibility
107
+ return new omdVariableNode(this._valueToString(value));
108
+ }
109
+
110
+ _valueToString(value) {
111
+ if (value instanceof omdNode) {
112
+ return value.toString();
113
+ }
114
+ if (typeof value === 'object' && value !== null) {
115
+ try {
116
+ const NodeClass = getNodeForAST(value);
117
+ const node = new NodeClass(value);
118
+ if (typeof node.initialize === 'function') node.initialize();
119
+ return node.toString();
120
+ } catch {
121
+ return String(value ?? '');
122
+ }
123
+ }
124
+ return String(value ?? '');
125
+ }
126
+
127
+ /**
128
+ * Disables background and interactions for an element and all its children
129
+ * @param {omdNode} element - The element to disable
130
+ * @private
131
+ */
132
+ _disableElement(element) {
133
+ if (!element) return;
134
+
135
+ this._hideElementBackground(element);
136
+ this._disableMouseInteractions(element);
137
+ this._disableHighlighting(element);
138
+ this._disableChildElements(element);
139
+ }
140
+
141
+ /**
142
+ * @private
143
+ */
144
+ _hideElementBackground(element) {
145
+ if (element.backRect) {
146
+ element.hideBackgroundByDefault();
147
+ element.backRect.setOpacity(0);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * @private
153
+ */
154
+ _disableMouseInteractions(element) {
155
+ if (element.svgObject) {
156
+ element.svgObject.onmouseenter = null;
157
+ element.svgObject.onmouseleave = null;
158
+ element.svgObject.style.cursor = "default";
159
+ }
160
+ }
161
+
162
+ /**
163
+ * @private
164
+ */
165
+ _disableHighlighting(element) {
166
+ // Override highlighting methods to prevent highlighting
167
+ element.setHighlight = () => {};
168
+ element.lowlight = () => {};
169
+ element.setFillColor = () => {};
170
+
171
+ // Force background to be transparent and stay that way
172
+ if (element.backRect) {
173
+ element.backRect.setOpacity(0);
174
+ element.backRect.setFillColor("transparent");
175
+ // Override the backRect methods too
176
+ element.backRect.setFillColor = () => {};
177
+ element.backRect.setOpacity = () => {};
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Makes this node completely non-highlightable
183
+ * @private
184
+ */
185
+ _makeNodeNonHighlightable() {
186
+ // Override all highlighting methods on this node
187
+ this.setHighlight = () => {};
188
+ this.lowlight = () => {};
189
+ this.setFillColor = () => {};
190
+
191
+ // Force background to be transparent and keep it that way
192
+ if (this.backRect) {
193
+ this.backRect.setOpacity(0);
194
+ this.backRect.setFillColor("transparent");
195
+ // Override backRect methods to prevent any changes
196
+ this.backRect.setFillColor = () => {};
197
+ this.backRect.setOpacity = () => {};
198
+ }
199
+ }
200
+
201
+ /**
202
+ * @private
203
+ */
204
+ _disableChildElements(element) {
205
+ if (element.childList) {
206
+ element.childList.forEach(child => this._disableElement(child));
207
+ }
208
+
209
+ const childProperties = ['left', 'right', 'base', 'exponent', 'argument', 'expression', 'numerator', 'denominator'];
210
+ childProperties.forEach(prop => {
211
+ if (element[prop]) this._disableElement(element[prop]);
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Computes the dimensions for the operation display node.
217
+ * Calculates the total width including both operations and the gap.
218
+ */
219
+ computeDimensions() {
220
+ // Ensure children are laid out before measuring
221
+ if (this.leftToken && typeof this.leftToken.updateLayout === 'function') this.leftToken.updateLayout();
222
+ if (this.rightToken && typeof this.rightToken.updateLayout === 'function') this.rightToken.updateLayout();
223
+
224
+ const leftWidth = this.leftToken ? this.leftToken.width : 0;
225
+ const rightWidth = this.rightToken ? this.rightToken.width : 0;
226
+
227
+ // Simple, fixed gap (keeps equals-centering predictable)
228
+ const gap = typeof this.gap === 'number' ? this.gap : 45;
229
+ this.gap = gap;
230
+
231
+ // For sequence alignment
232
+ this.leftClusterWidth = leftWidth;
233
+
234
+ const tallest = Math.max(this.leftToken ? this.leftToken.height : 0, this.rightToken ? this.rightToken.height : 0);
235
+ const verticalPadding = 6; // small, constant padding
236
+
237
+ const width = leftWidth + gap + rightWidth;
238
+ const height = tallest + verticalPadding * 2;
239
+ this.setWidthAndHeight(width, height);
240
+
241
+
242
+ }
243
+
244
+ /**
245
+ * Updates the layout of the operation display node.
246
+ * Positions both operations with appropriate spacing.
247
+ */
248
+ updateLayout() {
249
+ // Update children layouts first
250
+ [this.leftToken, this.rightToken].forEach(element => {
251
+ element.updateLayout();
252
+ });
253
+
254
+ this.computeDimensions();
255
+
256
+ const padding = 6;
257
+ const gap = this.gap || 30;
258
+
259
+ // Calculate positions for both operations using single tokens
260
+ const leftWidthNow = this.leftToken.width;
261
+
262
+ // Position left token
263
+ let x = 0;
264
+ this.leftToken.setPosition(x, (this.height - this.leftToken.height) / 2);
265
+
266
+ // Position right token
267
+ x = leftWidthNow + gap;
268
+ this.rightToken.setPosition(x, (this.height - this.rightToken.height) / 2);
269
+
270
+ // Show both operations
271
+ this.rightToken.show();
272
+
273
+ // Ensure this node remains non-highlightable after layout updates
274
+ this._makeNodeNonHighlightable();
275
+
276
+ super.updateLayout();
277
+ }
278
+
279
+ /**
280
+ * Returns the effective width of the left operation cluster for equals alignment
281
+ * Used by omdEquationSequenceNode to align like equations
282
+ * @returns {number}
283
+ */
284
+ getLeftWidthForAlignment() {
285
+ // Ensure dimensions are up to date
286
+ if (typeof this.leftClusterWidth !== 'number') {
287
+ this.computeDimensions();
288
+ }
289
+ return this.leftClusterWidth || 0;
290
+ }
291
+
292
+ /**
293
+ * Shows only the left operation (hides the right side)
294
+ */
295
+ showLeftOnly() {
296
+ this.rightToken.hide();
297
+
298
+ // Recalculate dimensions for left side only
299
+ const padding = 6;
300
+ const leftWidth = this.leftToken.width;
301
+ const fs2 = (typeof this.getFontSize === 'function') ? this.getFontSize() : 32;
302
+ const tallest = this.leftToken.height;
303
+ const verticalPadding = Math.ceil(fs2 * 0.35);
304
+ this.setWidthAndHeight(leftWidth, tallest + verticalPadding * 2);
305
+
306
+ // Reposition with only left side
307
+ let x = 0;
308
+ this.leftToken.setPosition(x, (this.height - this.leftToken.height) / 2);
309
+ }
310
+
311
+
312
+
313
+
314
+
315
+ clone() {
316
+ const clone = new omdOperationDisplayNode(this.operation, this.value);
317
+ clone.provenance.push(this.id);
318
+ // Ensure cloned node is also non-highlightable
319
+ clone._makeNodeNonHighlightable();
320
+ return clone;
321
+ }
322
+ }
@@ -0,0 +1,109 @@
1
+ import { omdLeafNode } from "./omdLeafNode.js";
2
+ import { omdColor } from "../../src/omdColor.js";
3
+ import { getMultiplicationSymbol } from "../config/omdConfigManager.js";
4
+
5
+ /**
6
+ * Leaf node that represents an operator symbol.
7
+ * @extends omdLeafNode
8
+ */
9
+ export class omdOperatorNode extends omdLeafNode {
10
+ /**
11
+ * Creates a leaf node from the AST data.
12
+ * @param {Object} astNodeData - The AST node containing leaf information.
13
+ */
14
+ constructor(nodeData) {
15
+ super(nodeData);
16
+ this.type = "omdOperatorNode";
17
+
18
+ this.opName = this.parseOpName(nodeData);
19
+
20
+ // Use configured multiplication symbol for display while keeping internal opName as '*'
21
+ const displaySymbol = this.opName === '*' ? getMultiplicationSymbol() : this.opName;
22
+ this.textElement = super.createTextElement(displaySymbol);
23
+ }
24
+
25
+ parseOpName(nodeData) {
26
+ if (typeof nodeData === "string")
27
+ return nodeData;
28
+
29
+ // Use a map for user-friendly display of operators
30
+ const operatorMap = {
31
+ 'multiply': getMultiplicationSymbol(),
32
+ 'divide': '÷',
33
+ 'add': '+',
34
+ 'subtract': '−',
35
+ 'pow': '^',
36
+ 'unaryMinus': '-',
37
+ 'unaryPlus': '+'
38
+ };
39
+
40
+ const op = nodeData.op || nodeData.fn;
41
+ return operatorMap[op] || op;
42
+ }
43
+
44
+ parseType() {
45
+ return "operator";
46
+ }
47
+
48
+ /**
49
+ * Calculates the dimensions of the node.
50
+ * Adds padding around the node.
51
+ * @override
52
+ */
53
+ computeDimensions() {
54
+ super.computeDimensions();
55
+
56
+ const ratio = this.getFontSize() / this.getRootFontSize();
57
+ const padding = 4 * ratio;
58
+ let paddedWidth = this.width + padding;
59
+ let paddedHeight = this.height + padding;
60
+ this.setWidthAndHeight(paddedWidth, paddedHeight);
61
+ }
62
+
63
+ /**
64
+ * Updates the layout of the node.
65
+ * @override
66
+ */
67
+ updateLayout() {
68
+ super.updateLayout();
69
+ }
70
+
71
+ /**
72
+ * Converts the omdOperatorNode to a math.js AST node.
73
+ * @returns {Object} A math.js-compatible AST node.
74
+ */
75
+ toMathJSNode() {
76
+ // This node is purely visual; its properties are used by its parent.
77
+ // It reconstructs a minimal AST for cloning purposes.
78
+ const astNode = {
79
+ type: 'OperatorNode',
80
+ op: this.opName,
81
+ fn: this.opName, // Simplification, may need adjustment for complex ops
82
+ args: []
83
+ };
84
+ astNode.clone = function() {
85
+ const clone = { ...this };
86
+ clone.argumentNodeList.argument = clone.argument;
87
+ return clone;
88
+ };
89
+ return astNode;
90
+ }
91
+
92
+ toString() {
93
+ return this.opName;
94
+ }
95
+
96
+ highlight(color) {
97
+ super.highlight(color);
98
+ if (this.opLabel) {
99
+ this.opLabel.setFillColor(omdColor.white);
100
+ }
101
+ }
102
+
103
+ clearProvenanceHighlights() {
104
+ super.clearProvenanceHighlights();
105
+ if (this.opLabel) {
106
+ this.opLabel.setFillColor(omdColor.text);
107
+ }
108
+ }
109
+ }