@teachinglab/omd 0.2.7 → 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.
- package/canvas/ui/toolbar.js +0 -15
- package/omd/core/omdEquationStack.js +11 -2
- package/omd/display/omdDisplay.js +75 -29
- package/omd/nodes/omdEquationNode.js +46 -6
- package/omd/step-visualizer/omdStepVisualizer.js +312 -29
- package/omd/step-visualizer/omdStepVisualizerLayout.js +122 -110
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +46 -8
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +318 -121
- package/package.json +1 -1
- package/src/omdBalanceHanger.js +31 -1
- package/src/omdColor.js +1 -0
- package/src/omdCoordinatePlane.js +53 -3
- package/src/omdMetaExpression.js +8 -4
- package/src/omdTable.js +182 -52
package/canvas/ui/toolbar.js
CHANGED
|
@@ -60,21 +60,6 @@ export class Toolbar {
|
|
|
60
60
|
|
|
61
61
|
// Add to main SVG so it is rendered
|
|
62
62
|
this.canvas.svg.appendChild(this.toolbarGroup.svgObject);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Check if the SVG is actually in the DOM
|
|
66
|
-
setTimeout(() => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// Check the actual SVG content
|
|
70
|
-
console.log('Toolbar SVG innerHTML:', this.toolbarGroup.svgObject.innerHTML);
|
|
71
|
-
console.log('Background SVG object:', this.background.svgObject);
|
|
72
|
-
console.log('Background SVG innerHTML:', this.background.svgObject.outerHTML);
|
|
73
|
-
|
|
74
|
-
// Check if the background is visible
|
|
75
|
-
console.log('Background fill color:', this.background.svgObject.getAttribute('fill'));
|
|
76
|
-
console.log('Background width/height:', this.background.svgObject.getAttribute('width'), this.background.svgObject.getAttribute('height'));
|
|
77
|
-
}, 100);
|
|
78
63
|
}
|
|
79
64
|
|
|
80
65
|
/**
|
|
@@ -34,7 +34,9 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
34
34
|
|
|
35
35
|
// The sequence is the core. If a visualizer is needed, that's our sequence.
|
|
36
36
|
if (options.stepVisualizer) {
|
|
37
|
-
|
|
37
|
+
// Pass through step visualizer styling options
|
|
38
|
+
const stepVisualizerStyling = this.stylingOptions?.stepVisualizer || {};
|
|
39
|
+
this.sequence = new omdStepVisualizer(steps, stepVisualizerStyling);
|
|
38
40
|
} else {
|
|
39
41
|
this.sequence = new omdEquationSequenceNode(steps);
|
|
40
42
|
}
|
|
@@ -44,6 +46,13 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
44
46
|
this.sequence.setDefaultEquationBackground(this.stylingOptions.equationBackground);
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
// Apply step visualizer background styling if provided
|
|
50
|
+
if (options.stepVisualizer && this.stylingOptions?.stepVisualizerBackground) {
|
|
51
|
+
if (typeof this.sequence.setBackgroundStyle === 'function') {
|
|
52
|
+
this.sequence.setBackgroundStyle(this.stylingOptions.stepVisualizerBackground);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
// If a toolbar is needed, create it.
|
|
48
57
|
if (this.toolbarOptions?.enabled) {
|
|
49
58
|
// Default undo: call global hook if provided
|
|
@@ -508,7 +517,7 @@ export class omdEquationStack extends jsvgGroup {
|
|
|
508
517
|
}
|
|
509
518
|
if (seq.layoutManager) {
|
|
510
519
|
try {
|
|
511
|
-
seq.layoutManager.updateVisualLayout();
|
|
520
|
+
seq.layoutManager.updateVisualLayout(true); // Allow repositioning for equation stack changes
|
|
512
521
|
seq.layoutManager.updateVisualVisibility();
|
|
513
522
|
seq.layoutManager.updateAllLinePositions();
|
|
514
523
|
} catch (_) {}
|
|
@@ -20,6 +20,7 @@ export class omdDisplay {
|
|
|
20
20
|
autoScale: true, // Automatically scale content to fit container
|
|
21
21
|
maxScale: 1, // Do not upscale beyond 1 by default
|
|
22
22
|
edgePadding: 16, // Horizontal padding from edges when scaling
|
|
23
|
+
autoCloseStepVisualizer: true, // Close active step visualizer text boxes before autoscale to avoid shrink
|
|
23
24
|
...options
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -399,6 +400,19 @@ export class omdDisplay {
|
|
|
399
400
|
const containerWidth = this.container.offsetWidth || 0;
|
|
400
401
|
const containerHeight = this.container.offsetHeight || 0;
|
|
401
402
|
|
|
403
|
+
// Early auto-close of step visualizer UI before measuring dimensions to avoid transient height inflation
|
|
404
|
+
if (this.options.autoCloseStepVisualizer && this.node) {
|
|
405
|
+
try {
|
|
406
|
+
if (typeof this.node.forceCloseAll === 'function') {
|
|
407
|
+
this.node.forceCloseAll();
|
|
408
|
+
} else if (typeof this.node.closeAllTextBoxes === 'function') {
|
|
409
|
+
this.node.closeAllTextBoxes();
|
|
410
|
+
} else if (typeof this.node.closeActiveDot === 'function') {
|
|
411
|
+
this.node.closeActiveDot();
|
|
412
|
+
}
|
|
413
|
+
} catch (e) { /* no-op */ }
|
|
414
|
+
}
|
|
415
|
+
|
|
402
416
|
// Determine actual content size (prefer sequence/current step when available)
|
|
403
417
|
let contentWidth = this.node.width || 0;
|
|
404
418
|
let contentHeight = this.node.height || 0;
|
|
@@ -406,38 +420,71 @@ export class omdDisplay {
|
|
|
406
420
|
const seq = this.node.getSequence();
|
|
407
421
|
if (seq) {
|
|
408
422
|
if (seq.width && seq.height) {
|
|
409
|
-
|
|
410
|
-
|
|
423
|
+
// For step visualizers, use sequenceWidth/Height instead of total dimensions to exclude visualizer elements from autoscale
|
|
424
|
+
contentWidth = seq.sequenceWidth || seq.width;
|
|
425
|
+
contentHeight = seq.sequenceHeight || seq.height;
|
|
426
|
+
console.log('omdDisplay: Using sequence dimensions for autoscale - sequenceWidth:', seq.sequenceWidth, 'sequenceHeight:', seq.sequenceHeight, 'width:', seq.width, 'height:', seq.height, 'contentWidth:', contentWidth, 'contentHeight:', contentHeight);
|
|
411
427
|
}
|
|
412
428
|
if (seq.getCurrentStep) {
|
|
413
429
|
const step = seq.getCurrentStep();
|
|
414
430
|
if (step && step.width && step.height) {
|
|
415
|
-
|
|
416
|
-
|
|
431
|
+
// For step visualizers, prioritize sequenceWidth/Height for dimension calculations
|
|
432
|
+
const stepWidth = seq.sequenceWidth || step.width;
|
|
433
|
+
const stepHeight = seq.sequenceHeight || step.height;
|
|
434
|
+
contentWidth = Math.max(contentWidth, stepWidth);
|
|
435
|
+
contentHeight = Math.max(contentHeight, stepHeight);
|
|
436
|
+
console.log('omdDisplay: Considering step dimensions - stepWidth:', stepWidth, 'stepHeight:', stepHeight, 'finalContentWidth:', contentWidth, 'finalContentHeight:', contentHeight);
|
|
417
437
|
}
|
|
418
438
|
}
|
|
419
439
|
}
|
|
420
440
|
}
|
|
441
|
+
console.log('omdDisplay: Final content dimensions for autoscale - width:', contentWidth, 'height:', contentHeight);
|
|
421
442
|
|
|
422
443
|
// Compute scale to keep within bounds
|
|
423
444
|
let scale = 1;
|
|
424
445
|
if (this.options.autoScale && contentWidth > 0 && contentHeight > 0) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
446
|
+
// Optionally close any open step visualizer textbox to prevent transient height expansion
|
|
447
|
+
if (this.options.autoCloseStepVisualizer && this.node) {
|
|
448
|
+
try {
|
|
449
|
+
if (typeof this.node.closeActiveDot === 'function') {
|
|
450
|
+
this.node.closeActiveDot();
|
|
451
|
+
} else if (typeof this.node.closeAllTextBoxes === 'function') {
|
|
452
|
+
this.node.closeAllTextBoxes();
|
|
453
|
+
}
|
|
454
|
+
} catch (e) { /* no-op */ }
|
|
455
|
+
}
|
|
456
|
+
// Detect step visualizer directly on node (getSequence returns underlying sequence only)
|
|
457
|
+
let hasStepVisualizer = false;
|
|
458
|
+
if (this.node) {
|
|
459
|
+
const ctorName = this.node.constructor?.name;
|
|
460
|
+
hasStepVisualizer = (ctorName === 'omdStepVisualizer') || this.node.type === 'omdStepVisualizer' || (typeof omdStepVisualizer !== 'undefined' && this.node instanceof omdStepVisualizer);
|
|
461
|
+
console.log('omdDisplay: Step visualizer detection (node) ctor:', ctorName, 'type:', this.node.type, 'detected:', hasStepVisualizer);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (hasStepVisualizer) {
|
|
465
|
+
// Preserve existing scale if already set on node; otherwise lock to 1.
|
|
466
|
+
const existingScale = (this.node && typeof this.node.scale === 'number') ? this.node.scale : undefined;
|
|
467
|
+
scale = (existingScale && existingScale > 0) ? existingScale : 1;
|
|
468
|
+
console.log('omdDisplay: Step visualizer detected - locking scale at', scale);
|
|
469
|
+
} else {
|
|
470
|
+
const hPad = this.options.edgePadding || 0;
|
|
471
|
+
const vPadTop = this.options.topMargin || 0;
|
|
472
|
+
const vPadBottom = this.options.bottomMargin || 0;
|
|
473
|
+
// Reserve extra space for overlay toolbar if needed
|
|
474
|
+
let reserveBottom = vPadBottom;
|
|
475
|
+
if (this.node && typeof this.node.isToolbarOverlay === 'function' && this.node.isToolbarOverlay()) {
|
|
476
|
+
const tH = (typeof this.node.getToolbarVisualHeight === 'function') ? this.node.getToolbarVisualHeight() : 0;
|
|
477
|
+
reserveBottom += (tH + (this.node.getOverlayPadding ? this.node.getOverlayPadding() : 16));
|
|
478
|
+
}
|
|
479
|
+
const availW = Math.max(0, containerWidth - hPad * 2);
|
|
480
|
+
const availH = Math.max(0, containerHeight - (vPadTop + reserveBottom));
|
|
481
|
+
const sx = availW > 0 ? (availW / contentWidth) : 1;
|
|
482
|
+
const sy = availH > 0 ? (availH / contentHeight) : 1;
|
|
483
|
+
const maxScale = (typeof this.options.maxScale === 'number') ? this.options.maxScale : 1;
|
|
484
|
+
scale = Math.min(sx, sy, maxScale);
|
|
485
|
+
if (!isFinite(scale) || scale <= 0) scale = 1;
|
|
486
|
+
console.log('omdDisplay: Autoscale calculation - availW:', availW, 'availH:', availH, 'contentW:', contentWidth, 'contentH:', contentHeight, 'scale:', scale);
|
|
433
487
|
}
|
|
434
|
-
const availW = Math.max(0, containerWidth - hPad * 2);
|
|
435
|
-
const availH = Math.max(0, containerHeight - (vPadTop + reserveBottom));
|
|
436
|
-
const sx = availW > 0 ? (availW / contentWidth) : 1;
|
|
437
|
-
const sy = availH > 0 ? (availH / contentHeight) : 1;
|
|
438
|
-
const maxScale = (typeof this.options.maxScale === 'number') ? this.options.maxScale : 1;
|
|
439
|
-
scale = Math.min(sx, sy, maxScale);
|
|
440
|
-
if (!isFinite(scale) || scale <= 0) scale = 1;
|
|
441
488
|
}
|
|
442
489
|
|
|
443
490
|
// Apply scale
|
|
@@ -451,14 +498,14 @@ export class omdDisplay {
|
|
|
451
498
|
const screenCenterX = containerWidth / 2;
|
|
452
499
|
x = screenCenterX - (this.node.alignPointX * scale);
|
|
453
500
|
} else {
|
|
454
|
-
const scaledWidth =
|
|
501
|
+
const scaledWidth = contentWidth * scale;
|
|
455
502
|
x = (containerWidth - scaledWidth) / 2;
|
|
456
503
|
}
|
|
457
504
|
|
|
458
505
|
// Decide whether positioning would move content outside container. If so,
|
|
459
506
|
// prefer expanding the SVG viewBox instead of moving nodes.
|
|
460
|
-
const scaledWidthFinal =
|
|
461
|
-
const scaledHeightFinal =
|
|
507
|
+
const scaledWidthFinal = contentWidth * scale;
|
|
508
|
+
const scaledHeightFinal = contentHeight * scale;
|
|
462
509
|
const totalNeededH = scaledHeightFinal + (this.options.topMargin || 0) + (this.options.bottomMargin || 0);
|
|
463
510
|
|
|
464
511
|
const willOverflowHoriz = scaledWidthFinal > containerWidth;
|
|
@@ -517,15 +564,17 @@ export class omdDisplay {
|
|
|
517
564
|
if (this.node.getSequence) {
|
|
518
565
|
const sequence = this.node.getSequence();
|
|
519
566
|
if (sequence && sequence.width && sequence.height) {
|
|
520
|
-
sequenceWidth
|
|
521
|
-
|
|
567
|
+
// For step visualizers, use sequenceWidth/Height instead of total dimensions to exclude visualizer elements from autoscale
|
|
568
|
+
sequenceWidth = sequence.sequenceWidth || sequence.width;
|
|
569
|
+
sequenceHeight = sequence.sequenceHeight || sequence.height;
|
|
522
570
|
|
|
523
571
|
// Check current step dimensions too
|
|
524
572
|
if (sequence.getCurrentStep) {
|
|
525
573
|
const currentStep = sequence.getCurrentStep();
|
|
526
574
|
if (currentStep && currentStep.width && currentStep.height) {
|
|
527
|
-
|
|
528
|
-
|
|
575
|
+
// For step visualizers, prioritize sequenceWidth/Height for dimension calculations
|
|
576
|
+
stepWidth = sequence.sequenceWidth || currentStep.width;
|
|
577
|
+
stepHeight = sequence.sequenceHeight || currentStep.height;
|
|
529
578
|
}
|
|
530
579
|
}
|
|
531
580
|
|
|
@@ -624,10 +673,7 @@ export class omdDisplay {
|
|
|
624
673
|
|
|
625
674
|
// Apply any stored font settings
|
|
626
675
|
if (this.options.fontFamily) {
|
|
627
|
-
// Small delay to ensure SVG elements are fully rendered
|
|
628
|
-
setTimeout(() => {
|
|
629
676
|
this.setFont(this.options.fontFamily, this.options.fontWeight || '400');
|
|
630
|
-
}, 10);
|
|
631
677
|
}
|
|
632
678
|
|
|
633
679
|
// Only use fitToContent for tight sizing when explicitly requested
|
|
@@ -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
|
|