@teachinglab/omd 0.2.6 → 0.2.8

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.
@@ -1005,7 +1005,7 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1005
1005
  case 'table':
1006
1006
  return this._renderToTable(mergedOptions);
1007
1007
  case 'hanger':
1008
- return this._renderToHanger();
1008
+ return this._renderToHanger(mergedOptions);
1009
1009
  case 'tileequation': {
1010
1010
  const leftExpr = this.getLeft().toString();
1011
1011
  const rightExpr = this.getRight().toString();
@@ -1089,6 +1089,11 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1089
1089
  tickInterval: (options.tickInterval !== undefined) ? options.tickInterval : 1,
1090
1090
  forceAllTickLabels: (options.forceAllTickLabels !== undefined) ? options.forceAllTickLabels : true,
1091
1091
  showTickLabels: (options.showTickLabels !== undefined) ? options.showTickLabels : true,
1092
+ // Background customization options
1093
+ backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
1094
+ backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
1095
+ backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
1096
+ showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
1092
1097
  graphEquations,
1093
1098
  lineSegments: [],
1094
1099
  dotValues: [],
@@ -1113,7 +1118,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1113
1118
  equation: `y = ${expr}`,
1114
1119
  xMin: options.xMin,
1115
1120
  xMax: options.xMax,
1116
- stepSize: options.stepSize
1121
+ stepSize: options.stepSize,
1122
+ // Background customization options
1123
+ backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
1124
+ backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
1125
+ backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
1126
+ showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
1127
+ // Alternating row color options
1128
+ alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
1129
+ evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
1130
+ oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
1131
+ alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
1117
1132
  };
1118
1133
  } else if (options.side === 'right') {
1119
1134
  const expr = this._normalizeExpressionString(this.getRight().toString());
@@ -1124,7 +1139,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1124
1139
  equation: `y = ${expr}`,
1125
1140
  xMin: options.xMin,
1126
1141
  xMax: options.xMax,
1127
- stepSize: options.stepSize
1142
+ stepSize: options.stepSize,
1143
+ // Background customization options
1144
+ backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
1145
+ backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
1146
+ backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
1147
+ showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
1148
+ // Alternating row color options
1149
+ alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
1150
+ evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
1151
+ oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
1152
+ alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
1128
1153
  };
1129
1154
  }
1130
1155
 
@@ -1155,7 +1180,17 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1155
1180
  omdType: "table",
1156
1181
  title: `Equation Table: ${this.toString()}`,
1157
1182
  headers,
1158
- data
1183
+ data,
1184
+ // Background customization options
1185
+ backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
1186
+ backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
1187
+ backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
1188
+ showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined,
1189
+ // Alternating row color options
1190
+ alternatingRowColors: (options.alternatingRowColors !== undefined) ? options.alternatingRowColors : undefined,
1191
+ evenRowColor: (options.evenRowColor !== undefined) ? options.evenRowColor : undefined,
1192
+ oddRowColor: (options.oddRowColor !== undefined) ? options.oddRowColor : undefined,
1193
+ alternatingRowOpacity: (options.alternatingRowOpacity !== undefined) ? options.alternatingRowOpacity : undefined
1159
1194
  };
1160
1195
  }
1161
1196
 
@@ -1184,7 +1219,7 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1184
1219
  * @returns {Object} JSON configuration for omdBalanceHanger
1185
1220
  * @private
1186
1221
  */
1187
- _renderToHanger() {
1222
+ _renderToHanger(options = {}) {
1188
1223
  // Convert equation sides to hanger representation
1189
1224
  const leftValues = this._convertToHangerValues(this.getLeft());
1190
1225
  const rightValues = this._convertToHangerValues(this.getRight());
@@ -1193,7 +1228,12 @@ _propagateBackgroundStyle(style, visited = new Set()) {
1193
1228
  omdType: "balanceHanger",
1194
1229
  leftValues: leftValues,
1195
1230
  rightValues: rightValues,
1196
- tilt: "none" // Equations should be balanced by definition
1231
+ tilt: "none", // Equations should be balanced by definition
1232
+ // Background customization options
1233
+ backgroundColor: (options.backgroundColor !== undefined) ? options.backgroundColor : undefined,
1234
+ backgroundCornerRadius: (options.backgroundCornerRadius !== undefined) ? options.backgroundCornerRadius : undefined,
1235
+ backgroundOpacity: (options.backgroundOpacity !== undefined) ? options.backgroundOpacity : undefined,
1236
+ showBackground: (options.showBackground !== undefined) ? options.showBackground : undefined
1197
1237
  };
1198
1238
  }
1199
1239
 
@@ -15,16 +15,22 @@ import { jsvgLayoutGroup, jsvgEllipse, jsvgLine } from '@teachinglab/jsvg';
15
15
  * @extends omdEquationSequenceNode
16
16
  */
17
17
  export class omdStepVisualizer extends omdEquationSequenceNode {
18
- constructor(steps) {
18
+ constructor(steps, styling = {}) {
19
19
  super(steps);
20
20
 
21
+ // Store styling options with defaults
22
+ this.styling = this._mergeWithDefaults(styling || {});
23
+
21
24
  // Visual elements for step tracking
22
25
  this.stepDots = [];
23
26
  this.stepLines = [];
24
27
  this.visualContainer = new jsvgLayoutGroup();
25
- this.dotRadius = getDotRadius(0);
26
- this.lineWidth = 2;
27
- this.visualSpacing = 30;
28
+
29
+ // Use styling values for these properties
30
+ this.dotRadius = this.styling.dotRadius;
31
+ this.lineWidth = this.styling.lineWidth;
32
+ this.visualSpacing = this.styling.visualSpacing;
33
+
28
34
  this.activeDotIndex = -1;
29
35
  this.dotsClickable = true;
30
36
  this.nodeToStepMap = new Map();
@@ -32,15 +38,311 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
32
38
  // Highlighting system
33
39
  this.stepVisualizerHighlights = new Set();
34
40
  this.highlighting = new omdStepVisualizerHighlighting(this);
35
- this.textBoxManager = new omdStepVisualizerTextBoxes(this, this.highlighting);
41
+
42
+ // Pass textbox options through styling parameter
43
+ const textBoxOptions = this.styling.textBoxOptions || {};
44
+ this.textBoxManager = new omdStepVisualizerTextBoxes(this, this.highlighting, textBoxOptions);
36
45
  this.layoutManager = new omdStepVisualizerLayout(this);
37
46
 
38
47
  this.addChild(this.visualContainer);
39
48
  this._initializeVisualElements();
49
+
50
+ // Set default filter level to show only major steps (stepMark = 0)
51
+ // This ensures intermediate steps are hidden and expansion dots can be created
52
+
53
+
54
+
55
+ if (this.setFilterLevel && typeof this.setFilterLevel === 'function') {
56
+ this.setFilterLevel(0);
57
+
58
+ } else {
59
+
60
+ }
61
+
40
62
  this.computeDimensions();
41
63
  this.updateLayout();
42
64
  }
43
65
 
66
+ /**
67
+ * Public: programmatically toggle a dot (simulate user click behavior)
68
+ * @param {number} dotIndex
69
+ */
70
+ toggleDot(dotIndex) {
71
+ if (typeof dotIndex !== 'number') return;
72
+ if (dotIndex < 0 || dotIndex >= this.stepDots.length) return;
73
+ const dot = this.stepDots[dotIndex];
74
+ this._handleDotClick(dot, dotIndex);
75
+ }
76
+
77
+ /**
78
+ * Public: close currently active dot textbox if any
79
+ */
80
+ closeActiveDot() {
81
+ // Always clear all boxes to be safe (even if activeDotIndex already reset)
82
+ try {
83
+ const before = this.textBoxManager?.stepTextBoxes?.length || 0;
84
+ this._clearActiveDot();
85
+ if (this.textBoxManager && typeof this.textBoxManager.clearAllTextBoxes === 'function') {
86
+ this.textBoxManager.clearAllTextBoxes();
87
+ }
88
+ const after = this.textBoxManager?.stepTextBoxes?.length || 0;
89
+ if (before !== after) console.log('omdStepVisualizer: closeActiveDot removed text boxes', before, '->', after);
90
+ } catch (e) { /* no-op */ }
91
+ }
92
+
93
+ /**
94
+ * Public: close all text boxes (future-proof; currently only one can be active)
95
+ */
96
+ closeAllTextBoxes() {
97
+ this.closeActiveDot();
98
+ }
99
+
100
+ /**
101
+ * Public: force close EVERYTHING related to active explanation UI
102
+ */
103
+ forceCloseAll() {
104
+ this.closeActiveDot();
105
+ }
106
+
107
+ /**
108
+ * Merges user styling with default styling options
109
+ * @param {Object} userStyling - User-provided styling options
110
+ * @returns {Object} Merged styling object with defaults
111
+ * @private
112
+ */
113
+ _mergeWithDefaults(userStyling) {
114
+ const defaults = {
115
+ // Dot styling
116
+ dotColor: omdColor.stepColor,
117
+ dotRadius: getDotRadius(0),
118
+ dotStrokeWidth: 2,
119
+ activeDotColor: omdColor.explainColor,
120
+ expansionDotScale: 0.4,
121
+
122
+ // Line styling
123
+ lineColor: omdColor.stepColor,
124
+ lineWidth: 2,
125
+ activeLineColor: omdColor.explainColor,
126
+
127
+ // Colors
128
+ explainColor: omdColor.explainColor,
129
+ highlightColor: omdColor.hiliteColor,
130
+
131
+ // Layout
132
+ visualSpacing: 30,
133
+ fixedVisualizerPosition: 250,
134
+ dotVerticalOffset: 15,
135
+
136
+ // Text boxes
137
+ textBoxOptions: {
138
+ backgroundColor: omdColor.white,
139
+ borderColor: omdColor.lightGray,
140
+ borderWidth: 1,
141
+ borderRadius: 5,
142
+ padding: 8, // Minimal padding for tight fit
143
+ fontSize: 14,
144
+ fontFamily: 'Albert Sans, Arial, sans-serif',
145
+ maxWidth: 300, // More reasonable width for compact layout
146
+ dropShadow: true
147
+ // Removed zIndex and position from defaults - these should only apply to container
148
+ },
149
+
150
+ // Visual effects
151
+ enableAnimations: true,
152
+ hoverEffects: true,
153
+
154
+ // Background styling (inherited from equation styling)
155
+ backgroundColor: null,
156
+ cornerRadius: null,
157
+ pill: null
158
+ };
159
+
160
+ return this._deepMerge(defaults, userStyling);
161
+ }
162
+
163
+ /**
164
+ * Deep merge two objects
165
+ * @param {Object} target - Target object
166
+ * @param {Object} source - Source object
167
+ * @returns {Object} Merged object
168
+ * @private
169
+ */
170
+ _deepMerge(target, source) {
171
+ const result = { ...target };
172
+
173
+ for (const key in source) {
174
+ if (source.hasOwnProperty(key)) {
175
+ if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
176
+ result[key] = this._deepMerge(result[key] || {}, source[key]);
177
+ } else {
178
+ result[key] = source[key];
179
+ }
180
+ }
181
+ }
182
+
183
+ return result;
184
+ }
185
+
186
+ /**
187
+ * Updates the styling options and applies them to existing visual elements
188
+ * @param {Object} newStyling - New styling options to apply
189
+ */
190
+ setStyling(newStyling) {
191
+ this.styling = this._mergeWithDefaults({ ...this.styling, ...newStyling });
192
+
193
+ // Update instance properties that are used elsewhere
194
+ this.dotRadius = this.styling.dotRadius;
195
+ this.lineWidth = this.styling.lineWidth;
196
+ this.visualSpacing = this.styling.visualSpacing;
197
+
198
+ this._applyStylingToExistingElements();
199
+
200
+ // Update layout spacing if changed
201
+ if (newStyling.visualSpacing !== undefined) {
202
+ this.visualSpacing = this.styling.visualSpacing;
203
+ }
204
+ if (newStyling.fixedVisualizerPosition !== undefined && this.layoutManager) {
205
+ this.layoutManager.setFixedVisualizerPosition(this.styling.fixedVisualizerPosition);
206
+ }
207
+
208
+ // Refresh layout and visual elements
209
+ this.updateLayout();
210
+ }
211
+
212
+ /**
213
+ * Gets the current styling options
214
+ * @returns {Object} Current styling configuration
215
+ */
216
+ getStyling() {
217
+ return { ...this.styling };
218
+ }
219
+
220
+ /**
221
+ * Sets a specific styling property
222
+ * @param {string} property - The property to set (supports dot notation like 'textBoxOptions.backgroundColor')
223
+ * @param {any} value - The value to set
224
+ */
225
+ setStyleProperty(property, value) {
226
+ const keys = property.split('.');
227
+ const lastKey = keys.pop();
228
+ const target = keys.reduce((obj, key) => {
229
+ if (!obj[key]) obj[key] = {};
230
+ return obj[key];
231
+ }, this.styling);
232
+
233
+ target[lastKey] = value;
234
+
235
+ // Update instance properties if they were changed
236
+ if (property === 'dotRadius') this.dotRadius = value;
237
+ if (property === 'lineWidth') this.lineWidth = value;
238
+ if (property === 'visualSpacing') this.visualSpacing = value;
239
+ if (property === 'fixedVisualizerPosition' && this.layoutManager) {
240
+ this.layoutManager.setFixedVisualizerPosition(value);
241
+ }
242
+
243
+ this._applyStylingToExistingElements();
244
+ this.updateLayout();
245
+ }
246
+
247
+ /**
248
+ * Gets a specific styling property
249
+ * @param {string} property - The property to get (supports dot notation)
250
+ * @returns {any} The property value
251
+ */
252
+ getStyleProperty(property) {
253
+ return property.split('.').reduce((obj, key) => obj?.[key], this.styling);
254
+ }
255
+
256
+ /**
257
+ * Applies current styling to all existing visual elements
258
+ * @private
259
+ */
260
+ _applyStylingToExistingElements() {
261
+ // Update dots
262
+ this.stepDots.forEach((dot, index) => {
263
+ if (dot && dot.equationRef) {
264
+ const isActive = this.activeDotIndex === index;
265
+ const color = isActive ? this.styling.activeDotColor : this.styling.dotColor;
266
+ dot.setFillColor(color);
267
+ dot.setStrokeColor(color);
268
+ dot.setStrokeWidth(this.styling.dotStrokeWidth);
269
+
270
+ // Update radius based on step mark
271
+ const stepMark = dot.equationRef.stepMark ?? 0;
272
+ const radius = this.styling.dotRadius || getDotRadius(stepMark);
273
+ dot.setWidthAndHeight(radius * 2, radius * 2);
274
+ dot.radius = radius;
275
+ }
276
+ });
277
+
278
+ // Update lines
279
+ this.stepLines.forEach((line, index) => {
280
+ if (line) {
281
+ const isActive = this.activeDotIndex >= 0 &&
282
+ (line.toDotIndex === this.activeDotIndex || line.fromDotIndex === this.activeDotIndex);
283
+ const color = isActive ? this.styling.activeLineColor : this.styling.lineColor;
284
+ line.setStrokeColor(color);
285
+ line.setStrokeWidth(this.styling.lineWidth);
286
+ }
287
+ });
288
+
289
+ // Update expansion dots
290
+ if (this.layoutManager && this.layoutManager.expansionDots) {
291
+ this.layoutManager.expansionDots.forEach(expansionDot => {
292
+ if (expansionDot) {
293
+ const baseRadius = this.styling.dotRadius || getDotRadius(0);
294
+ const radius = Math.max(3, baseRadius * this.styling.expansionDotScale);
295
+ expansionDot.setWidthAndHeight(radius * 2, radius * 2);
296
+ expansionDot.radius = radius;
297
+ expansionDot.setFillColor(this.styling.dotColor);
298
+ expansionDot.setStrokeColor(this.styling.dotColor);
299
+ }
300
+ });
301
+ }
302
+
303
+ // Update text box styling if manager exists
304
+ if (this.textBoxManager && typeof this.textBoxManager.updateStyling === 'function') {
305
+ this.textBoxManager.updateStyling(this.styling.textBoxOptions);
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Sets the visual background style (inherits from equation styling)
311
+ * @param {Object} style - Background style options
312
+ */
313
+ setBackgroundStyle(style = {}) {
314
+ this.styling.backgroundColor = style.backgroundColor || this.styling.backgroundColor;
315
+ this.styling.cornerRadius = style.cornerRadius || this.styling.cornerRadius;
316
+ this.styling.pill = style.pill !== undefined ? style.pill : this.styling.pill;
317
+
318
+ // Apply to equation background if this step visualizer has equation styling
319
+ if (typeof this.setDefaultEquationBackground === 'function') {
320
+ this.setDefaultEquationBackground(style);
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Gets the current background style
326
+ * @returns {Object} Current background style
327
+ */
328
+ getBackgroundStyle() {
329
+ return {
330
+ backgroundColor: this.styling.backgroundColor,
331
+ cornerRadius: this.styling.cornerRadius,
332
+ pill: this.styling.pill
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Sets the fixed position for the step visualizer
338
+ * @param {number} position - The x position from the left edge where the visualizer should be positioned
339
+ */
340
+ setFixedVisualizerPosition(position) {
341
+ if (this.layoutManager) {
342
+ this.layoutManager.setFixedVisualizerPosition(position);
343
+ }
344
+ }
345
+
44
346
  /**
45
347
  * Force rebuild visual container (dots/lines) from scratch
46
348
  */
@@ -77,7 +379,7 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
77
379
  });
78
380
 
79
381
  this.layoutManager.updateVisualZOrder();
80
- this.layoutManager.updateVisualLayout();
382
+ this.layoutManager.updateVisualLayout(true);
81
383
  }
82
384
 
83
385
  /**
@@ -85,12 +387,14 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
85
387
  * @private
86
388
  */
87
389
  _createStepDot(equation, index) {
88
- const radius = getDotRadius(equation.stepMark ?? 0);
390
+ const stepMark = equation.stepMark ?? 0;
391
+ const radius = this.styling.dotRadius || getDotRadius(stepMark);
89
392
  const dot = new jsvgEllipse();
90
393
  dot.setWidthAndHeight(radius * 2, radius * 2);
91
- dot.setFillColor(omdColor.stepColor);
92
- dot.setStrokeColor(omdColor.stepColor);
93
- dot.setStrokeWidth(this.lineWidth);
394
+ const dotColor = this.styling.dotColor;
395
+ dot.setFillColor(dotColor);
396
+ dot.setStrokeColor(dotColor);
397
+ dot.setStrokeWidth(this.styling.dotStrokeWidth);
94
398
  dot.radius = radius;
95
399
 
96
400
  dot.equationRef = equation;
@@ -114,8 +418,9 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
114
418
  */
115
419
  _createStepLine(fromIndex, toIndex) {
116
420
  const line = new jsvgLine();
117
- line.setStrokeColor(omdColor.stepColor);
118
- line.setStrokeWidth(this.lineWidth);
421
+ const lineColor = this.styling.lineColor;
422
+ line.setStrokeColor(lineColor);
423
+ line.setStrokeWidth(this.styling.lineWidth);
119
424
 
120
425
  line.fromDotIndex = fromIndex;
121
426
  line.toDotIndex = toIndex;
@@ -151,30 +456,39 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
151
456
  * Override addStep to update visual elements when new steps are added
152
457
  */
153
458
  addStep(step, options = {}) {
154
- let createdDot=null;
459
+ // Call parent first to add the step properly
460
+ super.addStep(step, options);
461
+
462
+ // Now create visual elements for equation nodes only
155
463
  if (step instanceof omdEquationNode) {
156
- this.steps.push(step);
157
- const equations = this.steps.filter(s => s instanceof omdEquationNode);
158
- const newIndex = equations.length - 1;
159
- this.steps.pop();
160
- createdDot = this._createStepDot(step, newIndex);
161
- step.findAllNodes().forEach(node => {
162
- this.nodeToStepMap.set(node.id, newIndex);
163
- });
464
+ // Find the actual index of this equation in the steps array
465
+ const equationIndex = this.steps.filter(s => s instanceof omdEquationNode).indexOf(step);
164
466
 
165
- if (newIndex > 0) {
166
- this._createStepLine(newIndex - 1, newIndex);
467
+ if (equationIndex >= 0) {
468
+ const createdDot = this._createStepDot(step, equationIndex);
469
+
470
+ // Update the node to step mapping
471
+ step.findAllNodes().forEach(node => {
472
+ this.nodeToStepMap.set(node.id, equationIndex);
473
+ });
474
+
475
+ // Create connecting line if this isn't the first equation
476
+ if (equationIndex > 0) {
477
+ this._createStepLine(equationIndex - 1, equationIndex);
478
+ }
479
+
480
+ // After stepMark is set, adjust dot radius
481
+ if (createdDot) {
482
+ const radius = getDotRadius(step.stepMark ?? 0);
483
+ createdDot.setWidthAndHeight(radius * 2, radius * 2);
484
+ createdDot.radius = radius;
485
+ }
167
486
  }
168
487
  }
169
488
 
170
- super.addStep(step, options);
171
-
172
- // after stepMark is set, adjust dot radius
173
- if(createdDot){
174
- const radius=getDotRadius(step.stepMark??0);
175
- createdDot.setWidthAndHeight(radius*2,radius*2);
176
- createdDot.radius=radius;
177
- }
489
+ // Update layout after adding the step
490
+ this.computeDimensions();
491
+ this.updateLayout();
178
492
  }
179
493
 
180
494
  /**
@@ -189,13 +503,16 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
189
503
  */
190
504
  computeDimensions() {
191
505
  super.computeDimensions();
506
+ // Store original dimensions before visualizer expansion
192
507
  this.sequenceWidth = this.width;
508
+ this.sequenceHeight = this.height;
193
509
 
194
- // Ensure stepDots is initialized before accessing its length
195
- if (this.stepDots && this.stepDots.length > 0) {
510
+ // Set width to include the fixed visualizer position plus visualizer width
511
+ if (this.stepDots && this.stepDots.length > 0 && this.layoutManager) {
196
512
  const containerWidth = this.dotRadius * 3;
197
- const visualWidth = this.visualSpacing + containerWidth;
198
- this.setWidthAndHeight(this.width + visualWidth, this.height);
513
+ const fixedVisualizerPosition = this.layoutManager.fixedVisualizerPosition || 250;
514
+ const totalWidth = fixedVisualizerPosition + this.visualSpacing + containerWidth;
515
+ this.setWidthAndHeight(totalWidth, this.height);
199
516
  }
200
517
  }
201
518
 
@@ -203,14 +520,26 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
203
520
  * Override updateLayout to update visual elements as well
204
521
  */
205
522
  updateLayout() {
523
+
524
+
206
525
  super.updateLayout();
207
526
 
208
527
  // Only update visual layout if layoutManager is initialized
209
528
  if (this.layoutManager) {
210
- this.layoutManager.updateVisualLayout();
529
+
530
+
531
+ this.layoutManager.updateVisualLayout(true); // Allow repositioning for main layout updates
532
+
533
+
211
534
  this.layoutManager.updateVisualVisibility();
535
+
536
+
212
537
  this.layoutManager.updateAllLinePositions();
538
+ } else {
539
+
213
540
  }
541
+
542
+
214
543
  }
215
544
 
216
545
  /**
@@ -313,30 +642,46 @@ export class omdStepVisualizer extends omdEquationSequenceNode {
313
642
  this.activeDot = this.stepDots[dotIndex];
314
643
 
315
644
  const dot = this.stepDots[dotIndex];
316
- dot.setFillColor(omdColor.explainColor);
317
- dot.setStrokeColor(omdColor.explainColor);
645
+ const explainColor = this.styling.activeDotColor;
646
+ dot.setFillColor(explainColor);
647
+ dot.setStrokeColor(explainColor);
318
648
 
319
- this.setLineAboveColor(dotIndex, omdColor.explainColor);
649
+ this.setLineAboveColor(dotIndex, this.styling.activeLineColor);
320
650
  this.textBoxManager.createTextBoxForDot(dotIndex);
651
+
652
+ // Temporarily disable equation repositioning for simple dot state changes
653
+ const originalRepositioning = this.layoutManager.allowEquationRepositioning;
654
+ this.layoutManager.allowEquationRepositioning = false;
321
655
  this.layoutManager.updateVisualZOrder();
656
+ this.layoutManager.allowEquationRepositioning = originalRepositioning;
322
657
  }
323
658
 
324
659
  /**
325
660
  * Clears the currently active dot
326
661
  * @private
662
+ */
663
+ /**
664
+ * Clears the currently active dot
665
+ * @private
327
666
  */
328
667
  _clearActiveDot() {
329
668
  try {
330
669
  if (this.activeDotIndex !== -1) {
331
670
  const dot = this.stepDots[this.activeDotIndex];
332
- dot.setFillColor(omdColor.stepColor);
333
- dot.setStrokeColor(omdColor.stepColor);
671
+ const dotColor = this.styling.dotColor;
672
+ dot.setFillColor(dotColor);
673
+ dot.setStrokeColor(dotColor);
334
674
 
335
- this.setLineAboveColor(this.activeDotIndex, omdColor.stepColor);
675
+ this.setLineAboveColor(this.activeDotIndex, this.styling.lineColor);
336
676
  this.textBoxManager.removeTextBoxForDot(this.activeDotIndex);
337
677
 
338
678
  this.highlighting.clearHighlights();
679
+
680
+ // Temporarily disable equation repositioning for simple dot state changes
681
+ const originalRepositioning = this.layoutManager.allowEquationRepositioning;
682
+ this.layoutManager.allowEquationRepositioning = false;
339
683
  this.layoutManager.updateVisualZOrder();
684
+ this.layoutManager.allowEquationRepositioning = originalRepositioning;
340
685
 
341
686
  this.activeDot = null;
342
687
  this.activeDotIndex = -1;