@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.
@@ -5,26 +5,27 @@ import { jsvgLayoutGroup, jsvgTextBox, jsvgRect } from '@teachinglab/jsvg';
5
5
  * Each step is a separate jsvgTextBox that can have hover interactions with the omdSequence
6
6
  */
7
7
  export class omdStepVisualizerInteractiveSteps {
8
- constructor(stepVisualizer, simplificationData) {
8
+ constructor(stepVisualizer, simplificationData, stylingOptions = {}) {
9
9
  this.stepVisualizer = stepVisualizer;
10
10
  this.simplificationData = simplificationData || {};
11
+ this.stylingOptions = stylingOptions || {};
11
12
  this.messages = this._extractMessages(simplificationData);
12
13
  this.ruleNames = this._extractRuleNames(simplificationData);
13
14
  this.stepElements = [];
14
15
  this.layoutGroup = new jsvgLayoutGroup();
15
- this.layoutGroup.setSpacer(20); // Much larger spacing to prevent clipping
16
-
17
- // Styling configuration
18
- this.stepWidth = 380; // Increased width to prevent text cutoff
19
- this.baseStepHeight = 40; // Increased base height
20
- this.headerHeight = 40;
21
- this.fontSize = 14;
16
+ this.layoutGroup.setSpacer(4); // Minimal spacing for tight layout
17
+
18
+ // Styling configuration with defaults that can be overridden
19
+ this.stepWidth = this.stylingOptions.maxWidth || 300; // Use maxWidth from styling options
20
+ this.baseStepHeight = 30; // Minimal height for tight fit
21
+ this.headerHeight = 28; // Minimal header height
22
+ this.fontSize = this.stylingOptions.fontSize || 14;
22
23
  this.smallFontSize = 12;
23
-
24
+
24
25
  this.setupLayoutGroup();
25
26
  this.createStepElements();
26
27
  }
27
-
28
+
28
29
  /**
29
30
  * Extracts messages from simplification data
30
31
  * @param {Object} data - Simplification data
@@ -33,14 +34,14 @@ export class omdStepVisualizerInteractiveSteps {
33
34
  */
34
35
  _extractMessages(data) {
35
36
  if (!data) return [];
36
-
37
+
37
38
  let messages = [];
38
39
  if (data.rawMessages && Array.isArray(data.rawMessages)) {
39
40
  messages = data.rawMessages;
40
41
  } else if (data.message) {
41
42
  messages = [data.message];
42
43
  }
43
-
44
+
44
45
  // Clean up messages - remove HTML tags and bullet points
45
46
  return messages.map(msg => {
46
47
  let clean = msg.replace(/<[^>]*>/g, ''); // Strip HTML tags
@@ -57,11 +58,11 @@ export class omdStepVisualizerInteractiveSteps {
57
58
  */
58
59
  _extractRuleNames(data) {
59
60
  if (!data) return ['Operation'];
60
-
61
+
61
62
  if (data.ruleNames && Array.isArray(data.ruleNames)) {
62
63
  return data.ruleNames;
63
64
  }
64
-
65
+
65
66
  // Default based on data type
66
67
  if (data.multipleSimplifications) {
67
68
  return ['Multiple Rules'];
@@ -75,45 +76,63 @@ export class omdStepVisualizerInteractiveSteps {
75
76
  * @private
76
77
  */
77
78
  setupLayoutGroup() {
78
- // Add background using explainColor for the entire step group
79
+ // Add background using styling options for the entire step group
79
80
  this.backgroundRect = new jsvgRect();
80
- this.backgroundRect.setWidthAndHeight(this.stepWidth + 24, 100); // Height will be updated, wider for increased padding
81
- this.backgroundRect.setFillColor(omdColor.lightGray);
82
- this.backgroundRect.setStrokeColor('#e0e0e0');
83
- this.backgroundRect.setStrokeWidth(1);
84
- this.backgroundRect.setCornerRadius(6);
81
+ this.backgroundRect.setWidthAndHeight(this.stepWidth + 16, 60); // Minimal padding and height for tight fit
82
+
83
+ // Apply styling options to the background container
84
+ const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
85
+ const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
86
+ const borderWidth = this.stylingOptions.borderWidth || 1;
87
+ const borderRadius = this.stylingOptions.borderRadius || 6;
88
+
89
+ this.backgroundRect.setFillColor(backgroundColor);
90
+ this.backgroundRect.setStrokeColor(borderColor);
91
+ this.backgroundRect.setStrokeWidth(borderWidth);
92
+ this.backgroundRect.setCornerRadius(borderRadius);
85
93
  this.backgroundRect.setPosition(0, 0); // Start at origin, not negative offset
94
+
95
+ // Apply drop shadow to the SVG element if requested
96
+ if (this.stylingOptions.dropShadow && this.backgroundRect.svgObject) {
97
+ this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
98
+ }
99
+
86
100
  this.layoutGroup.addChild(this.backgroundRect);
87
101
  }
88
-
102
+
89
103
  /**
90
104
  * Creates individual step elements from the messages array
91
105
  * @private
92
106
  */
93
107
  createStepElements() {
94
108
  if (!this.messages || this.messages.length === 0) return;
95
-
109
+
96
110
  // Create content container to separate from background
97
111
  this.contentGroup = new jsvgLayoutGroup();
98
- this.contentGroup.setSpacer(8); // Larger spacing between elements
99
- this.contentGroup.setPosition(12, 12); // More offset from background edge
100
-
112
+ this.contentGroup.setSpacer(2); // Minimal spacing between elements
113
+ this.contentGroup.setPosition(8, 8); // Minimal offset for tight fit
114
+
101
115
  if (this.messages.length === 1) {
102
-
116
+
103
117
  this.createSingleStepElement(this.messages[0], 0);
104
118
  } else {
105
119
 
106
120
  this.createMultipleStepElements();
107
121
  }
108
-
122
+
109
123
  this.contentGroup.doVerticalLayout();
110
124
  this.layoutGroup.addChild(this.contentGroup);
111
125
  this.updateBackgroundSize();
112
-
126
+
127
+ // Apply drop shadow after SVG element is created
128
+ setTimeout(() => {
129
+ this.applyDropShadowIfNeeded();
130
+ }, 10);
131
+
113
132
  // Debug logging
114
133
 
115
134
  }
116
-
135
+
117
136
  /**
118
137
  * Creates a single step element with header
119
138
  * @param {string} message - The step message
@@ -125,13 +144,13 @@ export class omdStepVisualizerInteractiveSteps {
125
144
  const ruleName = this.ruleNames[0] || 'Operation';
126
145
  const headerBox = this.createHeaderBox(ruleName + ':');
127
146
  this.contentGroup.addChild(headerBox);
128
-
147
+
129
148
  // Create the step box
130
149
  const stepBox = this.createStepTextBox(message, index, false);
131
150
  this.stepElements.push(stepBox);
132
151
  this.contentGroup.addChild(stepBox);
133
152
  }
134
-
153
+
135
154
  /**
136
155
  * Creates multiple step elements with header
137
156
  * @private
@@ -148,11 +167,11 @@ export class omdStepVisualizerInteractiveSteps {
148
167
  } else {
149
168
  headerText = `${this.ruleNames.length} Rules Applied:`;
150
169
  }
151
-
170
+
152
171
  const headerBox = this.createHeaderBox(headerText);
153
172
  this.contentGroup.addChild(headerBox);
154
173
  }
155
-
174
+
156
175
  // Create individual step elements
157
176
  this.messages.forEach((message, index) => {
158
177
  const stepBox = this.createStepTextBox(message, index, this.messages.length > 1);
@@ -160,7 +179,7 @@ export class omdStepVisualizerInteractiveSteps {
160
179
  this.contentGroup.addChild(stepBox);
161
180
  });
162
181
  }
163
-
182
+
164
183
  /**
165
184
  * Creates a header box with custom text
166
185
  * @param {string} headerText - Text to display in header
@@ -169,20 +188,20 @@ export class omdStepVisualizerInteractiveSteps {
169
188
  */
170
189
  createHeaderBox(headerText = 'Operation:') {
171
190
  const headerBox = new jsvgTextBox();
172
- const headerHeight = Math.max(this.headerHeight, 40); // Ensure minimum height
173
-
174
-
191
+ const headerHeight = Math.max(this.headerHeight, 50); // Increased minimum height
192
+
193
+
175
194
  headerBox.setWidthAndHeight(this.stepWidth, headerHeight);
176
195
  headerBox.setText(headerText);
177
196
  headerBox.setFontSize(this.fontSize);
178
197
  headerBox.setFontWeight('600');
179
198
  headerBox.setFontColor('#2c3e50');
180
-
181
- // Style the header with border
199
+
200
+ // Style the header with border and minimal spacing
182
201
  if (headerBox.div) {
183
202
  Object.assign(headerBox.div.style, {
184
203
  borderBottom: '1px solid #e0e0e0',
185
- padding: '8px 12px 6px 12px',
204
+ padding: '6px 8px 4px 8px', // Minimal padding for tight fit
186
205
  margin: '0',
187
206
  boxSizing: 'border-box',
188
207
  minHeight: `${headerHeight}px`,
@@ -190,14 +209,19 @@ export class omdStepVisualizerInteractiveSteps {
190
209
  whiteSpace: 'normal',
191
210
  wordWrap: 'break-word',
192
211
  overflowWrap: 'break-word',
193
- width: '100%'
212
+ width: '100%',
213
+ lineHeight: '1.2', // Tight line height
214
+ fontFamily: 'Albert Sans, Arial, sans-serif',
215
+ display: 'flex',
216
+ alignItems: 'center', // Center text vertically
217
+ justifyContent: 'flex-start'
194
218
  });
195
219
  }
196
220
 
197
-
221
+
198
222
  return headerBox;
199
223
  }
200
-
224
+
201
225
  /**
202
226
  * Creates an individual step text box
203
227
  * @param {string} message - Step message
@@ -208,33 +232,63 @@ export class omdStepVisualizerInteractiveSteps {
208
232
  */
209
233
  createStepTextBox(message, index, isMultiple) {
210
234
  const stepBox = new jsvgTextBox();
211
- const height = this.calculateStepHeight(message);
212
-
213
-
214
-
235
+ // Calculate actual height needed for content with minimal padding
236
+ const contentHeight = this.calculateContentHeight(message, index, isMultiple);
237
+ const height = Math.max(contentHeight, this.baseStepHeight); // Use calculated height
238
+
239
+
240
+
215
241
  stepBox.setWidthAndHeight(this.stepWidth, height);
216
242
  stepBox.setFontSize(this.fontSize);
217
243
  stepBox.setFontColor('#2c3e50');
218
-
244
+
219
245
  // Store step data for interactions
220
246
  stepBox.stepIndex = index;
221
247
  stepBox.stepMessage = message;
222
248
  stepBox.isMultiple = isMultiple;
223
-
249
+
224
250
  // Format the step content
225
251
  const formattedContent = this.formatStepContent(message, index, isMultiple);
226
-
252
+
227
253
  // Apply styling and content
228
254
  if (stepBox.div) {
229
- this.applyStepStyling(stepBox, formattedContent, isMultiple);
255
+ this.applyStepStyling(stepBox, formattedContent, isMultiple, height);
230
256
  this.setupStepInteractions(stepBox);
231
-
257
+
258
+ // Force the jsvgTextBox to respect our sizing
259
+ stepBox.div.style.height = `${height}px`;
260
+ stepBox.div.style.minHeight = `${height}px`;
261
+ stepBox.div.style.display = 'block';
262
+
263
+ // Add a more aggressive override after a delay to ensure it sticks
264
+ if (stepBox.div) {
265
+ const actualPadding = this.stylingOptions.padding || 6; // Get padding from styling options
266
+ stepBox.div.style.cssText += `
267
+ height: ${height}px !important;
268
+ min-height: ${height}px !important;
269
+ max-height: ${height}px !important;
270
+ padding: ${actualPadding}px ${actualPadding + 2}px !important;
271
+ line-height: 1.3 !important;
272
+ font-size: ${this.fontSize}px !important;
273
+ font-family: Albert Sans, Arial, sans-serif !important;
274
+ box-sizing: border-box !important;
275
+ display: flex !important;
276
+ flex-direction: column !important;
277
+ justify-content: center !important;
278
+ align-items: flex-start !important;
279
+ word-spacing: normal !important;
280
+ letter-spacing: normal !important;
281
+ transition: none !important;
282
+ transform: none !important;
283
+ animation: none !important;
284
+ `;
285
+ }
232
286
 
233
287
  }
234
-
288
+
235
289
  return stepBox;
236
290
  }
237
-
291
+
238
292
  /**
239
293
  * Formats the content for a step
240
294
  * @param {string} message - Raw message
@@ -246,16 +300,16 @@ export class omdStepVisualizerInteractiveSteps {
246
300
  formatStepContent(message, index, isMultiple) {
247
301
  const cleanMessage = message.trim();
248
302
  let content = '';
249
-
303
+
250
304
  // Only show step numbers for multiple steps
251
305
  if (isMultiple && this.messages.length > 1) {
252
- content += `<div class="step-number" style="color: #666; font-size: ${this.smallFontSize}px; margin: 0 0 2px 0; font-weight: 500;">Step ${index + 1}</div>`;
306
+ content += `<div class="step-number" style="color: #666; font-size: ${this.smallFontSize}px; margin: 0 0 2px 0; font-weight: 500; line-height: 1.2; font-family: Albert Sans, Arial, sans-serif;">Step ${index + 1}</div>`; // Minimal margin
253
307
  }
254
-
255
- content += '<div class="step-content" style="display: flex; align-items: flex-start; gap: 8px; margin: 0; width: 100%;">';
256
- content += '<span class="bullet" style="color: #666; font-weight: bold; flex-shrink: 0; margin-top: 2px;">•</span>';
257
- content += '<div class="step-text" style="margin: 0; flex: 1; min-width: 0; word-wrap: break-word; overflow-wrap: break-word;">';
258
-
308
+
309
+ content += '<div class="step-content" style="display: flex; align-items: center; gap: 6px; margin: 0; width: 100%; line-height: 1.3; font-family: Albert Sans, Arial, sans-serif;">'; // Center align and minimal spacing
310
+ content += '<span class="bullet" style="color: #666; font-weight: bold; flex-shrink: 0; font-size: 10px;">•</span>'; // Smaller bullet
311
+ content += '<div class="step-text" style="margin: 0; flex: 1; min-width: 0; word-wrap: break-word; overflow-wrap: break-word; line-height: 1.3; padding: 0;">';
312
+
259
313
  // Parse operation details
260
314
  if (this.isOperationMessage(cleanMessage)) {
261
315
  const action = this.extractOperationAction(cleanMessage);
@@ -263,54 +317,109 @@ export class omdStepVisualizerInteractiveSteps {
263
317
  const valueNode = this.extractOperationValueNode(cleanMessage);
264
318
 
265
319
  if (action && (value || valueNode)) {
266
- content += `<span style="font-weight: 600; color: #2c3e50;">${action}</span> `;
320
+ content += `<span style="font-weight: 600; color: #2c3e50; margin-right: 4px;">${action}</span> `;
267
321
  const displayValue = valueNode ? valueNode.toString() : value;
268
- content += `<span style="background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #d63384;">${displayValue}</span>`;
269
- content += `<span style="color: #666; font-size: ${this.smallFontSize}px;"> to both sides</span>`;
322
+ content += `<span style="background: #f5f5f5; padding: 3px 8px; border-radius: 4px; font-family: 'Courier New', monospace; color: #d63384; margin: 0 3px;">${displayValue}</span>`;
323
+ content += `<span style="color: #666; font-size: ${this.smallFontSize}px; margin-left: 4px;"> to both sides</span>`;
270
324
  } else {
271
- content += `<span>${cleanMessage}</span>`;
325
+ content += `<span style="padding: 2px 0;">${cleanMessage}</span>`;
272
326
  }
273
327
  } else {
274
- content += `<span style="font-weight: 500;">${cleanMessage}</span>`;
328
+ content += `<span style="font-weight: 500; padding: 2px 0;">${cleanMessage}</span>`;
275
329
  }
276
-
330
+
277
331
  content += '</div></div>';
278
332
  return content;
279
333
  }
280
-
334
+
281
335
  /**
282
336
  * Applies styling to a step text box
283
337
  * @param {jsvgTextBox} stepBox - The step box
284
338
  * @param {string} content - Formatted content
285
339
  * @param {boolean} isMultiple - Whether part of multiple steps
340
+ * @param {number} height - The calculated height for the step box
286
341
  * @private
287
342
  */
288
- applyStepStyling(stepBox, content, isMultiple) {
343
+ applyStepStyling(stepBox, content, isMultiple, height) {
344
+ const backgroundColor = this.stylingOptions.backgroundColor || omdColor.white;
345
+ const borderColor = this.stylingOptions.borderColor || omdColor.lightGray;
346
+ const borderWidth = this.stylingOptions.borderWidth || 1;
347
+ const borderRadius = this.stylingOptions.borderRadius || 4;
348
+ const padding = this.stylingOptions.padding || 6; // Minimal padding for tight fit
349
+
289
350
  const baseStyles = {
290
- padding: '8px 12px',
291
- borderRadius: '4px',
292
- transition: 'all 0.2s ease',
351
+ padding: `${padding}px ${padding + 2}px !important`, // Minimal padding for tight fit
352
+ borderRadius: `${borderRadius}px`,
353
+ border: `${borderWidth}px solid ${borderColor}`,
354
+ backgroundColor: backgroundColor,
293
355
  cursor: 'pointer',
294
- lineHeight: '1.5',
295
- backgroundColor: 'transparent',
296
- margin: '0',
356
+ transition: 'none !important', // Explicitly disable all transitions
357
+ transform: 'none !important', // Explicitly disable all transforms
358
+ animation: 'none !important', // Explicitly disable all animations
359
+ lineHeight: '1.3 !important', // Tight line height
360
+ margin: '0',
297
361
  boxSizing: 'border-box',
298
362
  overflow: 'visible',
299
- minHeight: `${this.baseStepHeight}px`,
363
+ minHeight: `${height}px !important`, // Use calculated height
364
+ height: `${height}px !important`, // Fixed height to content
300
365
  width: '100%',
301
- whiteSpace: 'normal', // Allow text wrapping
302
- wordWrap: 'break-word', // Break long words if necessary
303
- overflowWrap: 'break-word', // Modern standard for word breaking
304
- maxWidth: '100%' // Ensure content doesn't exceed container
366
+ whiteSpace: 'normal',
367
+ wordWrap: 'break-word',
368
+ overflowWrap: 'break-word',
369
+ maxWidth: '100%',
370
+ fontSize: `${this.fontSize}px !important`, // Force font size
371
+ fontFamily: 'Albert Sans, Arial, sans-serif !important', // Albert Sans font
372
+ display: 'flex !important', // Use flex for centering
373
+ flexDirection: 'column !important',
374
+ justifyContent: 'center !important', // Center content vertically
375
+ alignItems: 'flex-start !important'
305
376
  };
306
-
377
+
378
+ // Add drop shadow if requested
379
+ if (this.stylingOptions.dropShadow) {
380
+ baseStyles.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
381
+ }
382
+
383
+ // Set font family if specified
384
+ if (this.stylingOptions.fontFamily) {
385
+ baseStyles.fontFamily = this.stylingOptions.fontFamily;
386
+ }
387
+
307
388
  Object.assign(stepBox.div.style, baseStyles);
308
389
  stepBox.div.innerHTML = content;
309
-
390
+
391
+ // Additional CSS to force proper text spacing
392
+ if (stepBox.div) {
393
+ stepBox.div.style.cssText += `
394
+ padding: ${padding + 6}px ${padding + 10}px !important;
395
+ line-height: 1.7 !important;
396
+ min-height: ${this.baseStepHeight + 20}px !important;
397
+ font-size: ${this.fontSize}px !important;
398
+ display: flex !important;
399
+ flex-direction: column !important;
400
+ `;
401
+
402
+ // Apply proper layout to nested content - DO NOT use position absolute
403
+ const contentElements = stepBox.div.querySelectorAll('.step-content, .step-text');
404
+ contentElements.forEach(el => {
405
+ el.style.lineHeight = '1.3 !important';
406
+ el.style.margin = '0 !important';
407
+ el.style.fontFamily = 'Albert Sans, Arial, sans-serif !important';
408
+ // Remove any position absolute that might be inherited
409
+ el.style.position = 'static !important';
410
+ });
411
+
412
+ // Ensure bullet points and text spans stay in normal flow
413
+ const allSpans = stepBox.div.querySelectorAll('span');
414
+ allSpans.forEach(span => {
415
+ span.style.position = 'static !important';
416
+ });
417
+ }
418
+
310
419
  // Force a reflow to ensure proper sizing
311
420
  stepBox.div.offsetHeight;
312
421
  }
313
-
422
+
314
423
  /**
315
424
  * Sets up hover and click interactions for a step
316
425
  * @param {jsvgTextBox} stepBox - The step box
@@ -323,76 +432,97 @@ export class omdStepVisualizerInteractiveSteps {
323
432
  // Hover effects
324
433
  stepBox.div.addEventListener('mouseenter', () => {
325
434
  stepBox.div.style.backgroundColor = omdColor.mediumGray; // Slightly darker version of explainColor
326
- stepBox.div.style.transform = 'translateX(2px)';
327
-
435
+
328
436
  // Call hover callback if provided
329
437
  this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, true);
330
438
  });
331
-
439
+
332
440
  stepBox.div.addEventListener('mouseleave', () => {
333
441
  // Restore the original background color instead of setting to transparent
334
442
  stepBox.div.style.backgroundColor = originalBackgroundColor;
335
- stepBox.div.style.transform = 'translateX(0)';
336
-
443
+
337
444
  // Call hover callback if provided
338
445
  this.onStepHover?.(stepBox.stepIndex, stepBox.stepMessage, false);
339
446
  });
340
-
447
+
341
448
  // Click interactions
342
449
  stepBox.div.addEventListener('click', () => {
343
450
  this.onStepClick?.(stepBox.stepIndex, stepBox.stepMessage);
344
451
  });
345
452
  }
346
-
453
+
347
454
  /**
348
- * Calculates the height needed for a step box
455
+ * Calculates the exact height needed for content with minimal padding
349
456
  * @param {string} message - Step message
350
- * @returns {number} Height in pixels
457
+ * @param {number} index - Step index
458
+ * @param {boolean} isMultiple - Whether part of multiple steps
459
+ * @returns {number} Tight-fitting height in pixels
351
460
  * @private
352
461
  */
353
- calculateStepHeight(message) {
462
+ calculateContentHeight(message, index, isMultiple) {
354
463
  // Create a temporary element to measure actual text height
355
464
  const tempDiv = document.createElement('div');
356
465
  tempDiv.style.position = 'absolute';
357
466
  tempDiv.style.visibility = 'hidden';
358
- tempDiv.style.width = `${this.stepWidth - 24}px`; // Account for padding
467
+ tempDiv.style.width = `${this.stepWidth - 16}px`; // Account for minimal padding
359
468
  tempDiv.style.fontSize = `${this.fontSize}px`;
360
- tempDiv.style.lineHeight = '1.5';
361
- tempDiv.style.fontFamily = 'inherit';
469
+ tempDiv.style.lineHeight = '1.3'; // Tight line height
470
+ tempDiv.style.fontFamily = 'Albert Sans, Arial, sans-serif';
362
471
  tempDiv.style.whiteSpace = 'normal';
363
472
  tempDiv.style.wordWrap = 'break-word';
364
473
  tempDiv.style.overflowWrap = 'break-word';
365
- tempDiv.style.padding = '8px 12px';
474
+ tempDiv.style.padding = '6px 8px'; // Match the minimal padding
366
475
  tempDiv.style.boxSizing = 'border-box';
367
-
476
+ tempDiv.style.display = 'flex';
477
+ tempDiv.style.flexDirection = 'column';
478
+ tempDiv.style.justifyContent = 'center';
479
+
368
480
  // Use actual formatted content for measurement
369
- const isMultiple = this.messages && this.messages.length > 1;
370
- const formattedContent = this.formatStepContent(message, 0, isMultiple);
481
+ const formattedContent = this.formatStepContent(message, index, isMultiple);
371
482
  tempDiv.innerHTML = formattedContent;
372
-
483
+
373
484
  // Append to document to measure
374
485
  document.body.appendChild(tempDiv);
375
486
  const measuredHeight = tempDiv.offsetHeight;
376
487
  document.body.removeChild(tempDiv);
377
-
378
- // Ensure minimum height and add buffer for interactions
379
- const finalHeight = Math.max(this.baseStepHeight, measuredHeight + 8);
380
-
381
- return finalHeight;
488
+
489
+ // Return exact measured height with minimal buffer
490
+ return Math.max(this.baseStepHeight, measuredHeight + 2); // Just 2px buffer
382
491
  }
383
-
492
+
493
+ /**
494
+ * Calculates the height needed for a step box (legacy method, kept for compatibility)
495
+ * @param {string} message - Step message
496
+ * @returns {number} Height in pixels
497
+ * @private
498
+ */
499
+ calculateStepHeight(message) {
500
+ // Use the new tight-fitting calculation
501
+ return this.calculateContentHeight(message, 0, false);
502
+ }
503
+
384
504
  /**
385
505
  * Updates the background rectangle size after layout
386
506
  * @private
387
507
  */
388
508
  updateBackgroundSize() {
389
509
  if (this.backgroundRect && this.contentGroup) {
390
- const totalHeight = this.contentGroup.height + 24; // More padding for larger offset
391
- const totalWidth = this.stepWidth + 24; // More padding for larger offset
510
+ const totalHeight = this.contentGroup.height + 16; // Minimal padding for tight fit
511
+ const totalWidth = this.stepWidth + 16; // Minimal padding for tight fit
392
512
  this.backgroundRect.setWidthAndHeight(totalWidth, totalHeight);
393
513
  }
394
514
  }
395
-
515
+
516
+ /**
517
+ * Applies drop shadow to the background container if SVG element exists
518
+ * @private
519
+ */
520
+ applyDropShadowIfNeeded() {
521
+ if (this.stylingOptions.dropShadow && this.backgroundRect && this.backgroundRect.svgObject) {
522
+ this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
523
+ }
524
+ }
525
+
396
526
  /**
397
527
  * Sets callback for step hover events
398
528
  * @param {Function} callback - Function called with (stepIndex, message, isEntering)
@@ -400,7 +530,7 @@ export class omdStepVisualizerInteractiveSteps {
400
530
  setOnStepHover(callback) {
401
531
  this.onStepHover = callback;
402
532
  }
403
-
533
+
404
534
  /**
405
535
  * Sets callback for step click events
406
536
  * @param {Function} callback - Function called with (stepIndex, message)
@@ -408,7 +538,7 @@ export class omdStepVisualizerInteractiveSteps {
408
538
  setOnStepClick(callback) {
409
539
  this.onStepClick = callback;
410
540
  }
411
-
541
+
412
542
  /**
413
543
  * Gets the main layout group for adding to parent containers
414
544
  * @returns {jsvgLayoutGroup} The layout group
@@ -416,7 +546,7 @@ export class omdStepVisualizerInteractiveSteps {
416
546
  getLayoutGroup() {
417
547
  return this.layoutGroup;
418
548
  }
419
-
549
+
420
550
  /**
421
551
  * Sets the position of the entire step group
422
552
  * @param {number} x - X position
@@ -425,7 +555,7 @@ export class omdStepVisualizerInteractiveSteps {
425
555
  setPosition(x, y) {
426
556
  this.layoutGroup.setPosition(x, y);
427
557
  }
428
-
558
+
429
559
  /**
430
560
  * Gets the dimensions of the step group
431
561
  * @returns {Object} Width and height
@@ -436,21 +566,88 @@ export class omdStepVisualizerInteractiveSteps {
436
566
  height: this.backgroundRect ? this.backgroundRect.height : 100
437
567
  };
438
568
  }
439
-
569
+
570
+ /**
571
+ * Updates the styling options and re-applies them to existing elements
572
+ * @param {Object} newStylingOptions - New styling options
573
+ */
574
+ updateStyling(newStylingOptions = {}) {
575
+ this.stylingOptions = { ...this.stylingOptions, ...newStylingOptions };
576
+
577
+ // Update width if maxWidth changed
578
+ if (newStylingOptions.maxWidth) {
579
+ this.stepWidth = newStylingOptions.maxWidth;
580
+ }
581
+
582
+ // Update font size if changed
583
+ if (newStylingOptions.fontSize) {
584
+ this.fontSize = newStylingOptions.fontSize;
585
+ }
586
+
587
+ // Update background container styling
588
+ if (this.backgroundRect) {
589
+ const backgroundColor = this.stylingOptions.backgroundColor || omdColor.lightGray;
590
+ const borderColor = this.stylingOptions.borderColor || '#e0e0e0';
591
+ const borderWidth = this.stylingOptions.borderWidth || 1;
592
+ const borderRadius = this.stylingOptions.borderRadius || 6;
593
+
594
+ this.backgroundRect.setFillColor(backgroundColor);
595
+ this.backgroundRect.setStrokeColor(borderColor);
596
+ this.backgroundRect.setStrokeWidth(borderWidth);
597
+ this.backgroundRect.setCornerRadius(borderRadius);
598
+
599
+ // Apply or remove drop shadow
600
+ if (this.backgroundRect.svgObject) {
601
+ if (this.stylingOptions.dropShadow) {
602
+ this.backgroundRect.svgObject.style.filter = 'drop-shadow(0 2px 8px rgba(0,0,0,0.15))';
603
+ } else {
604
+ this.backgroundRect.svgObject.style.filter = '';
605
+ }
606
+ }
607
+ }
608
+
609
+ // Re-apply styling to all existing step elements
610
+ this.stepElements.forEach((stepBox, index) => {
611
+ if (stepBox.div) {
612
+ const content = stepBox.div.innerHTML;
613
+ // Calculate new height for the step
614
+ const height = this.calculateContentHeight(stepBox.stepMessage, index, stepBox.isMultiple);
615
+ this.applyStepStyling(stepBox, content, stepBox.isMultiple, height);
616
+
617
+ // Update font size
618
+ stepBox.setFontSize(this.fontSize);
619
+
620
+ // Update dimensions if needed
621
+ stepBox.setWidthAndHeight(this.stepWidth, height);
622
+ }
623
+ });
624
+
625
+ // Update background size
626
+ this.updateBackgroundSize();
627
+ }
628
+
629
+ /**
630
+ * Gets the current styling options
631
+ * @returns {Object} Current styling options
632
+ */
633
+ getStyling() {
634
+ return { ...this.stylingOptions };
635
+ }
636
+
440
637
  // Helper methods for message parsing (same as in formatter)
441
-
638
+
442
639
  isOperationMessage(message) {
443
640
  const operationKeywords = ['Applied', 'added', 'subtracted', 'multiplied', 'divided', 'both sides'];
444
- return operationKeywords.some(keyword =>
641
+ return operationKeywords.some(keyword =>
445
642
  message.toLowerCase().includes(keyword.toLowerCase())
446
643
  );
447
644
  }
448
-
645
+
449
646
  extractOperationAction(message) {
450
647
  const match = message.match(/^(Added|Subtracted|Multiplied|Divided)/i);
451
648
  return match ? match[0] : null;
452
649
  }
453
-
650
+
454
651
  extractOperationValue(message) {
455
652
  // Updated regex to handle simple values and expressions
456
653
  const match = message.match(/(?:Added|Subtracted|Multiplied|Divided)\s(.*?)\s(?:to|by)/i);
@@ -463,14 +660,14 @@ export class omdStepVisualizerInteractiveSteps {
463
660
  }
464
661
  return null;
465
662
  }
466
-
663
+
467
664
  extractOperationValueNode(message) {
468
665
  if (this.simplificationData && this.simplificationData.operationValueNode) {
469
666
  return this.simplificationData.operationValueNode;
470
667
  }
471
668
  return null;
472
669
  }
473
-
670
+
474
671
  /**
475
672
  * Destroys the step group and cleans up resources
476
673
  */