@teachinglab/omd 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # OMD (On-screen Math Display)
1
+ # OMD (Open Math Display)
2
2
 
3
3
  OMD is a JavaScript library for creating interactive mathematical interfaces in web applications. Build everything from simple equation displays to complex step-by-step solution systems with rich visual feedback and user interaction.
4
4
 
@@ -217,6 +217,8 @@ export class omdCanvas {
217
217
  const stroke = this.strokes.get(strokeId);
218
218
  if (!stroke) return false;
219
219
 
220
+ console.log('[Canvas Debug] Removing stroke:', strokeId, 'Remaining strokes:', this.strokes.size - 1);
221
+
220
222
  if (stroke.element.parentNode) {
221
223
  stroke.element.parentNode.removeChild(stroke.element);
222
224
  }
@@ -232,6 +234,8 @@ export class omdCanvas {
232
234
  * Clear all strokes
233
235
  */
234
236
  clear() {
237
+ console.log('[Canvas Debug] Clearing all strokes. Current count:', this.strokes.size);
238
+
235
239
  this.strokes.forEach((stroke, id) => {
236
240
  if (stroke.element.parentNode) {
237
241
  stroke.element.parentNode.removeChild(stroke.element);
@@ -241,6 +245,7 @@ export class omdCanvas {
241
245
  this.strokes.clear();
242
246
  this.selectedStrokes.clear();
243
247
 
248
+ console.log('[Canvas Debug] Canvas cleared. Stroke count now:', this.strokes.size);
244
249
  this.emit('cleared');
245
250
  }
246
251
 
package/canvas/index.js CHANGED
@@ -19,7 +19,7 @@ export { Cursor } from './ui/cursor.js';
19
19
 
20
20
  // Drawing objects
21
21
  export { Stroke } from './drawing/stroke.js';
22
- export { segment } from './drawing/Segment.js';
22
+ export { segment } from './drawing/segment.js';
23
23
 
24
24
  // Utilities
25
25
  export { BoundingBox } from './utils/boundingBox.js';
@@ -33,6 +33,8 @@ export class PencilTool extends Tool {
33
33
  onPointerDown(event) {
34
34
  if (!this.canUse()) return;
35
35
 
36
+ console.log('[Pencil Debug] Starting new stroke at:', event.x, event.y);
37
+
36
38
  this.isDrawing = true;
37
39
  this.points = [];
38
40
  this.lastPoint = { x: event.x, y: event.y };
@@ -51,7 +53,8 @@ export class PencilTool extends Tool {
51
53
  this.addPoint(event.x, event.y, event.pressure);
52
54
 
53
55
  // Add stroke to canvas
54
- this.canvas.addStroke(this.currentStroke);
56
+ const strokeId = this.canvas.addStroke(this.currentStroke);
57
+ console.log('[Pencil Debug] Added stroke to canvas with ID:', strokeId, 'Total strokes:', this.canvas.strokes.size);
55
58
 
56
59
  this.canvas.emit('strokeStarted', {
57
60
  stroke: this.currentStroke,
@@ -99,6 +102,8 @@ export class PencilTool extends Tool {
99
102
  // Finish the stroke
100
103
  this.currentStroke.finish();
101
104
 
105
+ console.log('[Pencil Debug] Finished stroke with', this.points.length, 'points. Canvas now has', this.canvas.strokes.size, 'strokes');
106
+
102
107
  this.canvas.emit('strokeCompleted', {
103
108
  stroke: this.currentStroke,
104
109
  tool: this.name,
@@ -1,6 +1,6 @@
1
1
  # OMD Library API Reference
2
2
 
3
- > This is the complete API reference for the OMD (On-screen Math Display) library. Use the table of contents below to navigate to the detailed documentation for each module and class.
3
+ > This is the complete API reference for the OMD (Open Math Display) library. Use the table of contents below to navigate to the detailed documentation for each module and class.
4
4
 
5
5
  ---
6
6
 
package/docs/index.md ADDED
@@ -0,0 +1,39 @@
1
+ # OMD Documentation
2
+
3
+ Welcome to the Open Math Display (OMD) documentation.
4
+
5
+ ## Getting Started
6
+
7
+ - [User Guide](user-guide.html) - Learn how to use OMD in your projects
8
+ - [API Reference](api-reference.html) - Complete API documentation
9
+
10
+ ## API Documentation
11
+
12
+ ### Core Classes
13
+ - [omdNode](api/omdNode.html) - Base node class
14
+ - [omdLeafNode](api/omdLeafNode.html) - Leaf nodes (numbers, variables)
15
+ - [omdGroupNode](api/omdGroupNode.html) - Group/container nodes
16
+ - [omdOperatorNode](api/omdOperatorNode.html) - Mathematical operators
17
+ - [omdFunctionNode](api/omdFunctionNode.html) - Function nodes
18
+ - [omdPowerNode](api/omdPowerNode.html) - Exponent/power nodes
19
+ - [omdRationalNode](api/omdRationalNode.html) - Fraction nodes
20
+ - [omdSqrtNode](api/omdSqrtNode.html) - Square root nodes
21
+
22
+ ### Display & Visualization
23
+ - [omdStepVisualizer](api/omdStepVisualizer.html) - Step-by-step visualization
24
+ - [omdSimplification](api/omdSimplification.html) - Expression simplification
25
+ - [omdPopup](api/omdPopup.html) - Popup overlay system
26
+
27
+ ### Utilities & Helpers
28
+ - [omdHelpers](api/omdHelpers.html) - Utility functions
29
+ - [eventManager](api/eventManager.html) - Event handling
30
+ - [focusFrameManager](api/focusFrameManager.html) - Focus frame management
31
+ - [configuration-options](api/configuration-options.html) - Configuration options
32
+
33
+ ## Examples
34
+
35
+ Visit the [examples directory](../examples/index.html) to see OMD in action.
36
+
37
+ ## Contributing
38
+
39
+ See the main [README](../readme.html) for information about contributing to OMD.
@@ -467,10 +467,18 @@ export class omdEquationStack extends jsvgGroup {
467
467
  // If this is a step visualizer, rebuild its dots/lines
468
468
  if (typeof seq.rebuildVisualizer === 'function') {
469
469
  try {
470
+ // Clear all step visualizer highlights before rebuilding
471
+ if (seq.highlighting && typeof seq.highlighting.clearAllExplainHighlights === 'function') {
472
+ seq.highlighting.clearAllExplainHighlights();
473
+ }
470
474
  seq.rebuildVisualizer();
471
475
  } catch (_) {}
472
476
  } else if (typeof seq._initializeVisualElements === 'function') {
473
477
  try {
478
+ // Clear all step visualizer highlights before rebuilding
479
+ if (seq.highlighting && typeof seq.highlighting.clearAllExplainHighlights === 'function') {
480
+ seq.highlighting.clearAllExplainHighlights();
481
+ }
474
482
  seq._initializeVisualElements();
475
483
  if (typeof seq.computeDimensions === 'function') seq.computeDimensions();
476
484
  if (typeof seq.updateLayout === 'function') seq.updateLayout();
@@ -1279,6 +1279,20 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1279
1279
  if (node.operation === 'add' || node.operation === 'plus') {
1280
1280
  values.push(...leftValues, ...rightValues);
1281
1281
  }
1282
+ // For subtraction, add left values and negate right values
1283
+ else if (node.operation === 'subtract' || node.operation === 'minus') {
1284
+ values.push(...leftValues);
1285
+ // For subtraction, we need to represent negative values
1286
+ for (const rightValue of rightValues) {
1287
+ if (typeof rightValue === 'number') {
1288
+ // Negate numeric values
1289
+ values.push(-rightValue);
1290
+ } else {
1291
+ // For variables/expressions, prepend with negative sign
1292
+ values.push(`-${rightValue}`);
1293
+ }
1294
+ }
1295
+ }
1282
1296
  // For multiplication, handle special cases
1283
1297
  else if (node.operation === 'multiply') {
1284
1298
  // Check if one operand is a constant (coefficient)
@@ -346,6 +346,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
346
346
  * Force rebuild visual container (dots/lines) from scratch
347
347
  */
348
348
  rebuildVisualizer() {
349
+ // Clear all step visualizer highlights before rebuilding
350
+ if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
351
+ this.highlighting.clearAllExplainHighlights();
352
+ }
353
+
349
354
  if (this.visualContainer) {
350
355
  this.removeChild(this.visualContainer);
351
356
  }
@@ -455,6 +460,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
455
460
  * Override addStep to update visual elements when new steps are added
456
461
  */
457
462
  addStep(step, options = {}) {
463
+ // Clear all step visualizer highlights when adding new steps (stack expansion)
464
+ if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
465
+ this.highlighting.clearAllExplainHighlights();
466
+ }
467
+
458
468
  // Call parent first to add the step properly
459
469
  super.addStep(step, options);
460
470
 
@@ -546,6 +556,11 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
546
556
  * @returns {boolean} Whether an operation was undone
547
557
  */
548
558
  undoLastOperation() {
559
+ // Clear all step visualizer highlights before undoing
560
+ if (this.highlighting && typeof this.highlighting.clearAllExplainHighlights === 'function') {
561
+ this.highlighting.clearAllExplainHighlights();
562
+ }
563
+
549
564
  // Remove bottom-most equation and its preceding operation display
550
565
  const beforeCount = this.steps.length;
551
566
  const removed = super.undoLastOperation ? super.undoLastOperation() : false;
@@ -592,6 +607,14 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
592
607
  */
593
608
  setDotsClickable(enabled) {
594
609
  this.dotsClickable = enabled;
610
+
611
+ // If disabling, clear any active highlights and dots
612
+ if (!enabled) {
613
+ this._clearActiveDot();
614
+ // Use the more thorough clearing to ensure no stale highlights remain
615
+ this.highlighting.clearAllExplainHighlights();
616
+ }
617
+
595
618
  this.stepDots.forEach(dot => {
596
619
  this.layoutManager.updateDotClickability(dot);
597
620
  });
@@ -674,7 +697,8 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
674
697
  this.setLineAboveColor(this.activeDotIndex, this.styling.lineColor);
675
698
  this.textBoxManager.removeTextBoxForDot(this.activeDotIndex);
676
699
 
677
- this.highlighting.clearHighlights();
700
+ // Use thorough clearing to ensure no stale highlights remain
701
+ this.highlighting.clearAllExplainHighlights();
678
702
 
679
703
  // Temporarily disable equation repositioning for simple dot state changes
680
704
  const originalRepositioning = this.layoutManager.allowEquationRepositioning;
@@ -101,6 +101,47 @@ export class omdStepVisualizerHighlighting {
101
101
  this.stepVisualizer.stepVisualizerHighlights.clear();
102
102
  }
103
103
 
104
+ /**
105
+ * Clears ALL explain highlights from the entire sequence, not just tracked ones.
106
+ * This is more thorough than clearHighlights() and should be used when
107
+ * the step visualizer is disabled or when we need to ensure no stale highlights remain.
108
+ */
109
+ clearAllExplainHighlights() {
110
+ // Clear tracked highlights first
111
+ this.clearHighlights();
112
+
113
+ // Also clear any explain highlights from the entire sequence tree
114
+ const rootNode = this.stepVisualizer.getRootNode ? this.stepVisualizer.getRootNode() : null;
115
+ if (rootNode) {
116
+ this._clearExplainHighlightsFromTree(rootNode);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Recursively clears explain highlights from an entire tree
122
+ * @param {omdNode} node - The root node to start clearing from
123
+ * @private
124
+ */
125
+ _clearExplainHighlightsFromTree(node) {
126
+ if (node && typeof node.setExplainHighlight === 'function') {
127
+ node.setExplainHighlight(false);
128
+ }
129
+
130
+ // Recursively clear from children
131
+ if (node && node.childList && Array.isArray(node.childList)) {
132
+ node.childList.forEach(child => {
133
+ this._clearExplainHighlightsFromTree(child);
134
+ });
135
+ }
136
+
137
+ // Also check argumentNodeList if it exists
138
+ if (node && node.argumentNodeList) {
139
+ Object.values(node.argumentNodeList).forEach(child => {
140
+ this._clearExplainHighlightsFromTree(child);
141
+ });
142
+ }
143
+ }
144
+
104
145
  /**
105
146
  * Highlights nodes in the previous equation based on provenance from changed nodes.
106
147
  * This creates a visual connection between the current changes and their origins.
@@ -723,6 +723,11 @@ export class omdStepVisualizerLayout {
723
723
  _handleExpansionDotClick(expansionDot) {
724
724
  const sv = this.stepVisualizer;
725
725
 
726
+ // Clear all step visualizer highlights when expanding/contracting
727
+ if (sv.highlighting && typeof sv.highlighting.clearAllExplainHighlights === 'function') {
728
+ sv.highlighting.clearAllExplainHighlights();
729
+ }
730
+
726
731
  if (expansionDot.isCollapseDot) {
727
732
  // Handle collapse dot click - hide only the specific group of intermediate steps
728
733
 
@@ -73,20 +73,19 @@ export class omdPopup {
73
73
 
74
74
  // Store original opacities for animation
75
75
  const originalPopupOpacity = this.popup.opacity;
76
- const originalCanvasOpacity = this.penCanvas?.container?.style.opacity || '1';
77
76
  const originalTextInputOpacity = this.popupTextInput?.div?.style.opacity || '1';
78
77
 
79
- // Ensure canvas and text input are visible for animation
78
+ // Ensure canvas and text input are visible for animation but preserve canvas drawing
80
79
  if (this.penCanvas && this.penCanvas.container) {
81
80
  this.penCanvas.container.style.display = 'block';
82
- this.penCanvas.container.style.opacity = originalCanvasOpacity;
81
+ // Don't fade canvas opacity - keep strokes visible during popup hide
83
82
  }
84
83
  if (this.popupTextInput && this.popupTextInput.div) {
85
84
  this.popupTextInput.div.style.display = 'flex';
86
85
  this.popupTextInput.div.style.opacity = originalTextInputOpacity;
87
86
  }
88
87
 
89
- // Animate all elements together
88
+ // Animate popup and canvas together
90
89
  const duration = this.options.animationDuration || 300;
91
90
  const startTime = performance.now();
92
91
 
@@ -103,7 +102,7 @@ export class omdPopup {
103
102
  this.popup.setOpacity(currentOpacity);
104
103
  }
105
104
 
106
- // Animate canvas with proper opacity setting
105
+ // Animate canvas container with same opacity curve
107
106
  if (this.penCanvas && this.penCanvas.container) {
108
107
  this.penCanvas.container.style.opacity = currentOpacity;
109
108
  }
@@ -116,9 +115,10 @@ export class omdPopup {
116
115
  if (progress < 1) {
117
116
  this.popupAnimationId = requestAnimationFrame(animate);
118
117
  } else {
119
- // Animation complete - hide and cleanup
118
+ // Animation complete - hide and cleanup both popup and canvas
120
119
  if (this.penCanvas && this.penCanvas.container) {
121
120
  this.penCanvas.container.style.display = 'none';
121
+ this.penCanvas.container.style.opacity = '1'; // Reset for next show
122
122
  }
123
123
  if (this.popupTextInput && this.popupTextInput.div) {
124
124
  this.popupTextInput.div.style.display = 'none';
@@ -131,7 +131,7 @@ export class omdPopup {
131
131
 
132
132
  return new Promise((resolve) => {
133
133
  setTimeout(resolve, duration);
134
- });
134
+ });
135
135
  }
136
136
 
137
137
  /**
@@ -475,19 +475,22 @@ export class omdPopup {
475
475
  // Add click debugging
476
476
  canvasContainer.addEventListener('click', (e) => {});
477
477
 
478
- // Store cleanup function
479
- this.penCanvasCleanup = () => {
480
- if (this.penCanvas) {
481
- this.penCanvas.destroy();
482
- }
483
- if (foreignObject && foreignObject.parentNode) {
484
- foreignObject.parentNode.removeChild(foreignObject);
485
- } else if (canvasContainer && canvasContainer.parentNode) {
486
- canvasContainer.parentNode.removeChild(canvasContainer);
487
- }
488
- };
489
-
490
- // If we're currently in pen mode, show the canvas
478
+ // Store cleanup function
479
+ this.penCanvasCleanup = () => {
480
+ if (this.penCanvas) {
481
+ this.penCanvas.destroy();
482
+ }
483
+ if (foreignObject && foreignObject.parentNode) {
484
+ foreignObject.parentNode.removeChild(foreignObject);
485
+ } else if (canvasContainer && canvasContainer.parentNode) {
486
+ canvasContainer.parentNode.removeChild(canvasContainer);
487
+ }
488
+ // Remove step visualizer listeners
489
+ this._removeStepVisualizerListeners();
490
+ };
491
+
492
+ // Set up step visualizer change detection
493
+ this._setupStepVisualizerListeners(); // If we're currently in pen mode, show the canvas
491
494
  if (this.currentMode === 'pen' && this.popup) {
492
495
  this._addCanvasToParent(foreignObject || canvasContainer);
493
496
  }
@@ -510,6 +513,12 @@ export class omdPopup {
510
513
  if (!this.penCanvas) {
511
514
  this._createPenCanvas();
512
515
  } else {
516
+ // Show existing canvas and ensure it's fully opaque
517
+ const element = this.penCanvas.foreignObject || this.penCanvas.container;
518
+ if (element) {
519
+ element.style.display = 'block';
520
+ element.style.opacity = '1'; // Ensure full opacity for stroke visibility
521
+ }
513
522
  this._addCanvasToParent();
514
523
  }
515
524
  }
@@ -679,12 +688,22 @@ export class omdPopup {
679
688
  const easedProgress = 1 - Math.pow(1 - progress, 3);
680
689
  const currentOpacity = fromOpacity + (deltaOpacity * easedProgress);
681
690
 
691
+ // Animate popup
682
692
  this.popup.setOpacity(currentOpacity);
683
693
 
694
+ // Animate canvas with same opacity if it exists
695
+ if (this.penCanvas && this.penCanvas.container && this.currentMode === 'pen') {
696
+ this.penCanvas.container.style.opacity = currentOpacity;
697
+ }
698
+
684
699
  if (progress < 1) {
685
700
  this.popupAnimationId = requestAnimationFrame(animate);
686
701
  } else {
687
702
  this.popupAnimationId = null;
703
+ // Ensure canvas is fully opaque when animation completes
704
+ if (this.penCanvas && this.penCanvas.container && this.currentMode === 'pen') {
705
+ this.penCanvas.container.style.opacity = '1';
706
+ }
688
707
  resolve();
689
708
  }
690
709
  };
@@ -820,6 +839,9 @@ export class omdPopup {
820
839
  this.resizeObserver = null;
821
840
  }
822
841
 
842
+ // Clean up step visualizer listeners
843
+ this._removeStepVisualizerListeners();
844
+
823
845
  // Clean up animation
824
846
  if (this.popupAnimationId) {
825
847
  cancelAnimationFrame(this.popupAnimationId);
@@ -897,6 +919,87 @@ export class omdPopup {
897
919
  this.resizeObserver.observe(document.body);
898
920
  }
899
921
 
922
+ /**
923
+ * Setup step visualizer listeners to track expand/collapse
924
+ * @private
925
+ */
926
+ _setupStepVisualizerListeners() {
927
+ if (!this.penCanvas) return;
928
+
929
+ // Listen for step visualizer events that might change layout
930
+ this._stepVisualizerUpdateHandler = () => {
931
+ console.log('[Step Visualizer Debug] Layout change detected, updating canvas position');
932
+ // Small delay to allow layout to settle
933
+ setTimeout(() => this._updateCanvasPosition(), 50);
934
+ };
935
+
936
+ // Listen for various events that might indicate step visualizer changes
937
+ document.addEventListener('click', this._stepVisualizerUpdateHandler);
938
+ window.addEventListener('resize', this._stepVisualizerUpdateHandler);
939
+
940
+ // Listen for custom step visualizer events if they exist
941
+ if (this.parentElement && this.parentElement.element) {
942
+ this.parentElement.element.addEventListener('stepVisualizerChanged', this._stepVisualizerUpdateHandler);
943
+ }
944
+
945
+ // Set up mutation observer to detect DOM changes in step visualizer
946
+ if (this.parentElement && this.parentElement.element) {
947
+ this._mutationObserver = new MutationObserver((mutations) => {
948
+ let shouldUpdate = false;
949
+ for (const mutation of mutations) {
950
+ // Check if any changes might affect layout
951
+ if (mutation.type === 'attributes' &&
952
+ (mutation.attributeName === 'style' ||
953
+ mutation.attributeName === 'class' ||
954
+ mutation.attributeName === 'transform')) {
955
+ console.log('[Step Visualizer Debug] Mutation detected:', mutation.attributeName, mutation.target);
956
+ shouldUpdate = true;
957
+ break;
958
+ }
959
+ if (mutation.type === 'childList' &&
960
+ (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
961
+ console.log('[Step Visualizer Debug] Child list mutation detected');
962
+ shouldUpdate = true;
963
+ break;
964
+ }
965
+ }
966
+ if (shouldUpdate) {
967
+ setTimeout(() => this._updateCanvasPosition(), 10);
968
+ }
969
+ });
970
+
971
+ // Observe the parent element and its children
972
+ this._mutationObserver.observe(this.parentElement.element, {
973
+ childList: true,
974
+ attributes: true,
975
+ subtree: true,
976
+ attributeFilter: ['style', 'class', 'transform', 'viewBox']
977
+ });
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Remove step visualizer listeners
983
+ * @private
984
+ */
985
+ _removeStepVisualizerListeners() {
986
+ if (this._stepVisualizerUpdateHandler) {
987
+ document.removeEventListener('click', this._stepVisualizerUpdateHandler);
988
+ window.removeEventListener('resize', this._stepVisualizerUpdateHandler);
989
+
990
+ if (this.parentElement && this.parentElement.element) {
991
+ this.parentElement.element.removeEventListener('stepVisualizerChanged', this._stepVisualizerUpdateHandler);
992
+ }
993
+
994
+ this._stepVisualizerUpdateHandler = null;
995
+ }
996
+
997
+ if (this._mutationObserver) {
998
+ this._mutationObserver.disconnect();
999
+ this._mutationObserver = null;
1000
+ }
1001
+ }
1002
+
900
1003
  /**
901
1004
  * Update canvas position to match popup
902
1005
  * @private
@@ -907,6 +1010,13 @@ export class omdPopup {
907
1010
  const container = this.penCanvas.container;
908
1011
  const popupRect = this.popup.svgObject ? this.popup.svgObject.getBoundingClientRect() : null;
909
1012
 
1013
+ console.log('[Canvas Position Debug] Update triggered:', {
1014
+ hasCanvas: !!this.penCanvas,
1015
+ hasContainer: !!container,
1016
+ strokeCount: this.penCanvas?.strokes?.size || 0,
1017
+ popupRect: popupRect ? `${popupRect.width}x${popupRect.height}` : 'null'
1018
+ });
1019
+
910
1020
  if (popupRect && popupRect.width > 0 && popupRect.height > 0) {
911
1021
  // Calculate button areas based on popup dimensions
912
1022
  const leftButtonArea = this.buttonSize + (this.margin * 2);
@@ -927,10 +1037,21 @@ export class omdPopup {
927
1037
  const finalWidth = Math.min(Math.max(contentWidth, this.canvasMinWidth), maxWidth);
928
1038
  const finalHeight = Math.min(Math.max(contentHeight, this.canvasMinHeight), maxHeight);
929
1039
 
1040
+ console.log('[Canvas Position Debug] Moving canvas:', {
1041
+ from: `${container.style.left}, ${container.style.top}`,
1042
+ to: `${absoluteX}px, ${absoluteY}px`,
1043
+ size: `${finalWidth}x${finalHeight}`
1044
+ });
1045
+
930
1046
  container.style.left = `${absoluteX}px`;
931
1047
  container.style.top = `${absoluteY}px`;
932
1048
  container.style.width = `${finalWidth}px`;
933
1049
  container.style.height = `${finalHeight}px`;
1050
+
1051
+ // Check stroke count after move
1052
+ setTimeout(() => {
1053
+ console.log('[Canvas Position Debug] After move stroke count:', this.penCanvas?.strokes?.size || 0);
1054
+ }, 50);
934
1055
  }
935
1056
  }
936
1057
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teachinglab/omd",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "omd",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -32,20 +32,26 @@
32
32
  "author": "jaredschiffman",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
+ "@netlify/vite-plugin": "^2.5.4",
36
+ "@teachinglab/jsvg": "0.1.1",
35
37
  "dotenv": "^17.2.1",
36
38
  "express": "^4.18.2",
37
39
  "mathjs": "^14.5.2",
38
40
  "openai": "^4.28.0",
39
- "@teachinglab/jsvg": "0.1.1",
40
- "@netlify/vite-plugin": "^2.5.4",
41
41
  "vite": "^5.4.0"
42
42
  },
43
43
  "scripts": {
44
- "dev": "vite",
45
- "build": "vite build",
46
- "dev:netlify": "netlify dev"
44
+ "dev": "npm run build:docs && vite",
45
+ "build": "vite build && node copy-static.js && node build-docs.js",
46
+ "build:docs": "node build-docs.js",
47
+ "build:static": "node copy-static.js",
48
+ "dev:netlify": "npm run build:docs && netlify dev"
47
49
  },
48
50
  "publishConfig": {
49
51
  "access": "public"
52
+ },
53
+ "devDependencies": {
54
+ "glob": "^11.0.3",
55
+ "marked": "^16.2.1"
50
56
  }
51
57
  }
package/readme.html ADDED
@@ -0,0 +1,320 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OMD (Open Math Display) - OMD Documentation</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ max-width: 1200px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ background: #fff;
16
+ }
17
+ .header {
18
+ border-bottom: 1px solid #eee;
19
+ margin-bottom: 2rem;
20
+ padding-bottom: 1rem;
21
+ }
22
+ .header h1 {
23
+ margin: 0;
24
+ color: #2c3e50;
25
+ }
26
+ .nav {
27
+ margin: 1rem 0;
28
+ }
29
+ .nav a {
30
+ color: #3498db;
31
+ text-decoration: none;
32
+ margin-right: 1rem;
33
+ padding: 0.5rem 1rem;
34
+ border-radius: 4px;
35
+ transition: background-color 0.2s;
36
+ }
37
+ .nav a:hover {
38
+ background-color: #f8f9fa;
39
+ text-decoration: none;
40
+ }
41
+ .content h1, .content h2, .content h3, .content h4, .content h5, .content h6 {
42
+ color: #2c3e50;
43
+ margin-top: 2rem;
44
+ margin-bottom: 1rem;
45
+ }
46
+ .content h1 {
47
+ border-bottom: 2px solid #3498db;
48
+ padding-bottom: 0.5rem;
49
+ }
50
+ .content h2 {
51
+ border-bottom: 1px solid #eee;
52
+ padding-bottom: 0.3rem;
53
+ }
54
+ .content pre {
55
+ background: #f8f9fa;
56
+ border: 1px solid #e9ecef;
57
+ border-radius: 4px;
58
+ padding: 1rem;
59
+ overflow-x: auto;
60
+ }
61
+ .content code {
62
+ background: #f8f9fa;
63
+ padding: 0.2rem 0.4rem;
64
+ border-radius: 3px;
65
+ font-family: 'SFMono-Regular', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
66
+ }
67
+ .content pre code {
68
+ background: none;
69
+ padding: 0;
70
+ }
71
+ .content blockquote {
72
+ border-left: 4px solid #3498db;
73
+ margin: 1rem 0;
74
+ padding-left: 1rem;
75
+ color: #666;
76
+ }
77
+ .content table {
78
+ border-collapse: collapse;
79
+ width: 100%;
80
+ margin: 1rem 0;
81
+ }
82
+ .content th, .content td {
83
+ border: 1px solid #ddd;
84
+ padding: 0.75rem;
85
+ text-align: left;
86
+ }
87
+ .content th {
88
+ background: #f8f9fa;
89
+ font-weight: 600;
90
+ }
91
+ .content ul {
92
+ margin: 1rem 0;
93
+ }
94
+ .content li {
95
+ margin: 0.25rem 0;
96
+ }
97
+ .content a {
98
+ color: #3498db;
99
+ text-decoration: none;
100
+ }
101
+ .content a:hover {
102
+ text-decoration: underline;
103
+ }
104
+ .back-to-top {
105
+ position: fixed;
106
+ bottom: 20px;
107
+ right: 20px;
108
+ background: #3498db;
109
+ color: white;
110
+ padding: 10px 15px;
111
+ border-radius: 50px;
112
+ text-decoration: none;
113
+ opacity: 0.8;
114
+ transition: opacity 0.2s;
115
+ }
116
+ .back-to-top:hover {
117
+ opacity: 1;
118
+ text-decoration: none;
119
+ color: white;
120
+ }
121
+ @media (max-width: 768px) {
122
+ body {
123
+ padding: 10px;
124
+ }
125
+ .nav {
126
+ flex-direction: column;
127
+ }
128
+ .nav a {
129
+ display: block;
130
+ margin: 0.25rem 0;
131
+ }
132
+ }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div class="header">
137
+ <h1>OMD Documentation</h1>
138
+ <div class="nav">
139
+ <a href="./../index.html">Home</a>
140
+ <a href="./index.html">Documentation</a>
141
+ <a href="./api-reference.html">API Reference</a>
142
+ <a href="./../examples/index.html">Examples</a>
143
+ <a href="./../readme.html">README</a>
144
+ </div>
145
+ </div>
146
+
147
+ <div class="content">
148
+ <h1 id="omd-open-math-display">OMD (Open Math Display)</h1>
149
+ <p>OMD is a JavaScript library for creating interactive mathematical interfaces in web applications. Build everything from simple equation displays to complex step-by-step solution systems with rich visual feedback and user interaction.</p>
150
+ <p><img src="https://i.imgur.com/CdtEi33.png" alt="OMD Demo"></p>
151
+ <h2 id="features">Features</h2>
152
+ <h3 id="interactive-math-rendering"><strong>Interactive Math Rendering</strong></h3>
153
+ <ul>
154
+ <li>High-quality SVG-based mathematical notation</li>
155
+ <li>Real-time expression manipulation and visualization</li>
156
+ <li>Automatic layout and alignment for complex equations</li>
157
+ </ul>
158
+ <h3 id="step-by-step-solutions"><strong>Step-by-Step Solutions</strong></h3>
159
+ <ul>
160
+ <li>Visual step tracking with detailed explanations</li>
161
+ <li>Simplification engine with rule-based transformations</li>
162
+ <li>Provenance tracking for highlighting related elements</li>
163
+ </ul>
164
+ <h3 id="rich-ui-components"><strong>Rich UI Components</strong></h3>
165
+ <ul>
166
+ <li>Built-in toolbar for common mathematical operations</li>
167
+ <li>Drag &amp; drop interface for intuitive manipulation</li>
168
+ <li>Customizable canvas for multi-expression layouts</li>
169
+ </ul>
170
+ <h3 id="educational-features"><strong>Educational Features</strong></h3>
171
+ <ul>
172
+ <li>Interactive learning experiences</li>
173
+ <li>Progressive step revelation</li>
174
+ <li>Visual operation feedback and highlighting</li>
175
+ </ul>
176
+ <h2 id="installation">Installation</h2>
177
+ <h3 id="npm">npm</h3>
178
+ <pre><code class="language-bash">npm install @teachinglab/omd
179
+ </code></pre>
180
+ <h2 id="basic-usage">Basic Usage</h2>
181
+ <pre><code class="language-javascript">import { omdDisplay } from &#39;@teachinglab/omd&#39;;
182
+
183
+ // Create a math display
184
+ const container = document.getElementById(&#39;math-container&#39;);
185
+ const display = new omdDisplay(container);
186
+
187
+ // Render an equation
188
+ display.render(&#39;2x + 3 = 11&#39;);
189
+ </code></pre>
190
+ <h3 id="step-by-step-solutions">Step-by-Step Solutions</h3>
191
+ <pre><code class="language-javascript">import { omdEquationStack, omdEquationNode } from &#39;@teachinglab/omd&#39;;
192
+
193
+ // Create solution steps
194
+ const steps = [
195
+ omdEquationNode.fromString(&#39;2x + 3 = 11&#39;),
196
+ omdEquationNode.fromString(&#39;2x = 8&#39;),
197
+ omdEquationNode.fromString(&#39;x = 4&#39;)
198
+ ];
199
+
200
+ // Create interactive equation stack
201
+ const stack = new omdEquationStack(steps, {
202
+ toolbar: true,
203
+ stepVisualizer: true
204
+ });
205
+
206
+ display.render(stack);
207
+ </code></pre>
208
+ <h2 id="core-concepts">Core Concepts</h2>
209
+ <h3 id="nodes-building-blocks"><strong>Nodes</strong> - Building Blocks</h3>
210
+ <p>Every mathematical element is a node in an expression tree:</p>
211
+ <ul>
212
+ <li><code>omdEquationNode</code> - Complete equations (e.g., <code>2x + 3 = 11</code>)</li>
213
+ <li><code>omdConstantNode</code> - Numbers (e.g., <code>5</code>, <code>3.14</code>)</li>
214
+ <li><code>omdVariableNode</code> - Variables (e.g., <code>x</code>, <code>y</code>)</li>
215
+ <li><code>omdBinaryExpressionNode</code> - Operations (e.g., <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>)</li>
216
+ </ul>
217
+ <h3 id="sequences-solution-steps"><strong>Sequences</strong> - Solution Steps</h3>
218
+ <p>Group related equations for step-by-step solving:</p>
219
+ <pre><code class="language-javascript">const sequence = new omdEquationSequenceNode([
220
+ equation1, equation2, equation3
221
+ ]);
222
+ </code></pre>
223
+ <h3 id="display-rendering-engine"><strong>Display</strong> - Rendering Engine</h3>
224
+ <p>Handles layout, centering, and visualization:</p>
225
+ <pre><code class="language-javascript">const display = new omdDisplay(container, {
226
+ fontSize: 36,
227
+ centerContent: true
228
+ });
229
+ </code></pre>
230
+ <h2 id="interactive-examples">Interactive Examples</h2>
231
+ <p>Explore OMD&#39;s capabilities with our comprehensive examples:</p>
232
+ <table>
233
+ <thead>
234
+ <tr>
235
+ <th>Category</th>
236
+ <th>Example</th>
237
+ <th>Description</th>
238
+ </tr>
239
+ </thead>
240
+ <tbody><tr>
241
+ <td><strong>Getting Started</strong></td>
242
+ <td><a href="examples/minimal.html">Minimal</a></td>
243
+ <td>Basic equation rendering</td>
244
+ </tr>
245
+ <tr>
246
+ <td></td>
247
+ <td><a href="examples/simple-usage.html">Simple Usage</a></td>
248
+ <td>Interactive features</td>
249
+ </tr>
250
+ <tr>
251
+ <td><strong>Advanced</strong></td>
252
+ <td><a href="examples/expression-playground.html">Expression Playground</a></td>
253
+ <td>Full manipulation interface</td>
254
+ </tr>
255
+ <tr>
256
+ <td></td>
257
+ <td><a href="examples/drag-and-drop-playground.html">Drag &amp; Drop</a></td>
258
+ <td>Intuitive interaction</td>
259
+ </tr>
260
+ <tr>
261
+ <td><strong>Educational</strong></td>
262
+ <td><a href="examples/worked-solution.html">Worked Solutions</a></td>
263
+ <td>Step-by-step solving</td>
264
+ </tr>
265
+ <tr>
266
+ <td></td>
267
+ <td><a href="examples/kids-interactive.html">Kids Interactive</a></td>
268
+ <td>Child-friendly interface</td>
269
+ </tr>
270
+ <tr>
271
+ <td><strong>Components</strong></td>
272
+ <td><a href="examples/equation-stack-test.html">Equation Stack</a></td>
273
+ <td>Stacked equations</td>
274
+ </tr>
275
+ <tr>
276
+ <td></td>
277
+ <td><a href="examples/canvas-multiple-nodes.html">Canvas Demo</a></td>
278
+ <td>Multi-expression layouts</td>
279
+ </tr>
280
+ </tbody></table>
281
+ <p><strong><a href="examples/index.html">Browse All Examples</a></strong></p>
282
+ <h2 id="documentation">Documentation</h2>
283
+ <table>
284
+ <thead>
285
+ <tr>
286
+ <th>Resource</th>
287
+ <th>Description</th>
288
+ </tr>
289
+ </thead>
290
+ <tbody><tr>
291
+ <td><strong><a href="docs/api-reference.html">API Reference</a></strong></td>
292
+ <td>Complete component documentation</td>
293
+ </tr>
294
+ <tr>
295
+ <td><strong><a href="docs/user-guide.html">User Guide</a></strong></td>
296
+ <td>Getting started and tutorials</td>
297
+ </tr>
298
+ </tbody></table>
299
+ <h2 id="architecture">Architecture</h2>
300
+ <pre><code>OMD Library Structure
301
+ ├── Display Layer (omdDisplay)
302
+ ├── Node System (Expression tree components)
303
+ ├── UI Components (Toolbar, Step visualizer)
304
+ ├── Core Systems (Simplification, Layout)
305
+ └── Utilities (Configuration, Helpers)
306
+ </code></pre>
307
+ <h2 id="dependencies">Dependencies</h2>
308
+ <ul>
309
+ <li><strong>JSVG</strong> - High-performance SVG rendering</li>
310
+ <li><strong>math.js</strong> - Mathematical expression parsing</li>
311
+ <li><strong>Modern Browser</strong> - ES6 modules, SVG support</li>
312
+ </ul>
313
+ <hr>
314
+ <p><strong>Ready to get started?</strong> Check out our <a href="examples/index.html">examples</a> or dive into the <a href="docs/api-reference.html">documentation</a>!</p>
315
+
316
+ </div>
317
+
318
+ <a href="#" class="back-to-top">↑ Top</a>
319
+ </body>
320
+ </html>
@@ -1,9 +0,0 @@
1
- # OMD User Guide
2
-
3
- This user guide is under construction. For now, please refer to the following resources:
4
-
5
- - [API Reference](./api-reference.md)
6
- - [Documentation](../DOCUMENTATION.md)
7
- - [Examples](../examples/index.html)
8
-
9
- If you are looking for something specific, please check the API docs or the main documentation file. More detailed guides and tutorials will be added soon.
File without changes