diff --git a/index.html b/index.html
index a372f9f..86d0b75 100644
--- a/index.html
+++ b/index.html
@@ -160,10 +160,19 @@
diff --git a/src/js/app.js b/src/js/app.js
index a7c7407..29b1214 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -378,6 +378,19 @@ document.addEventListener('DOMContentLoaded', function () {
switchViewMode('published');
});
+ // Preview fit mode buttons
+ document.getElementById('preview-fit-default').addEventListener('click', () => {
+ setPreviewFitMode('default');
+ });
+
+ document.getElementById('preview-fit-width').addEventListener('click', () => {
+ setPreviewFitMode('width');
+ });
+
+ document.getElementById('preview-fit-full').addEventListener('click', () => {
+ setPreviewFitMode('full');
+ });
+
// Publish and Revert buttons
document.getElementById('publish-btn').addEventListener('click', publishDraft);
document.getElementById('revert-btn').addEventListener('click', revertDraft);
diff --git a/src/js/editor.js b/src/js/editor.js
index 999fe15..a61d9cf 100644
--- a/src/js/editor.js
+++ b/src/js/editor.js
@@ -1,3 +1,34 @@
+// Track preview fit mode
+let previewFitMode = 'default'; // 'default', 'width', or 'full'
+
+// Apply fit mode to spec dimensions
+function applyPreviewFitMode(spec, fitMode) {
+ if (!spec || typeof spec !== 'object') return spec;
+
+ // Clone to avoid mutation
+ const modifiedSpec = JSON.parse(JSON.stringify(spec));
+
+ if (fitMode === 'width') {
+ // Fit to width - get preview pane width
+ const previewPane = document.getElementById('vega-preview');
+ if (previewPane) {
+ const containerWidth = previewPane.offsetWidth;
+ modifiedSpec.width = containerWidth - 10; // 10px padding for scroll
+ // Keep original aspect ratio by not setting height
+ }
+ } else if (fitMode === 'full') {
+ // Fit to full pane - get both dimensions
+ const previewPane = document.getElementById('vega-preview');
+ if (previewPane) {
+ modifiedSpec.width = previewPane.offsetWidth - 10; // 10px padding
+ modifiedSpec.height = previewPane.offsetHeight - 10; // 10px padding
+ }
+ }
+ // 'default' mode leaves original dimensions untouched
+
+ return modifiedSpec;
+}
+
// Resolve dataset references in a spec
async function resolveDatasetReferences(spec) {
// If spec has data.name, look it up
@@ -83,12 +114,18 @@ async function renderVisualization() {
// Resolve dataset references
spec = await resolveDatasetReferences(spec);
+ // Apply preview fit mode
+ spec = applyPreviewFitMode(spec, previewFitMode);
+
// Render with Vega-Embed (use global variable)
await window.vegaEmbed('#vega-preview', spec, {
actions: false, // Hide action menu for cleaner look
renderer: 'svg' // Use SVG for better quality
});
+ // Hide overlay after successful render
+ hidePreviewOverlay();
+
} catch (error) {
// Handle rendering errors gracefully
previewContainer.innerHTML = `
@@ -99,6 +136,9 @@ async function renderVisualization() {
Check your JSON syntax and Vega-Lite specification.
`;
+
+ // Hide overlay after error
+ hidePreviewOverlay();
}
}
@@ -110,6 +150,9 @@ function debouncedRender() {
return;
}
+ // Show overlay to indicate rendering is pending
+ showPreviewOverlay();
+
clearTimeout(renderTimeout);
const debounceTime = getSetting('performance.renderDebounce') || 1500;
renderTimeout = setTimeout(renderVisualization, debounceTime);
@@ -121,6 +164,42 @@ function updateRenderDebounce(newDebounce) {
// No need to do anything special here
}
+// Set preview fit mode and update button states
+function setPreviewFitMode(mode) {
+ previewFitMode = mode;
+
+ // Update button states
+ document.getElementById('preview-fit-default').classList.toggle('active', mode === 'default');
+ document.getElementById('preview-fit-width').classList.toggle('active', mode === 'width');
+ document.getElementById('preview-fit-full').classList.toggle('active', mode === 'full');
+
+ // Re-render with new fit mode
+ renderVisualization();
+}
+
+// Show preview overlay (dims the visualization)
+function showPreviewOverlay() {
+ const overlay = document.getElementById('preview-overlay');
+ if (overlay) {
+ overlay.style.display = 'block';
+ // Trigger reflow to enable transition
+ void overlay.offsetHeight;
+ overlay.classList.add('active');
+ }
+}
+
+// Hide preview overlay
+function hidePreviewOverlay() {
+ const overlay = document.getElementById('preview-overlay');
+ if (overlay) {
+ overlay.classList.remove('active');
+ // Wait for transition to finish before hiding
+ setTimeout(() => {
+ overlay.style.display = 'none';
+ }, 200);
+ }
+}
+
// Load Vega libraries dynamically with UMD builds
function loadVegaLibraries() {
return new Promise((resolve, reject) => {
diff --git a/src/js/panel-manager.js b/src/js/panel-manager.js
index ea3cef4..12e9630 100644
--- a/src/js/panel-manager.js
+++ b/src/js/panel-manager.js
@@ -148,6 +148,11 @@ function initializeResize() {
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
+ // Show overlay if resizing preview panel (in non-default fit mode)
+ if (typeof previewFitMode !== 'undefined' && previewFitMode !== 'default') {
+ showPreviewOverlay();
+ }
+
e.preventDefault();
});
});
@@ -194,6 +199,11 @@ function initializeResize() {
updatePanelMemory();
saveLayoutToStorage();
+
+ // Re-render preview if fit mode is not 'default' (adaptive re-render on resize)
+ if (typeof previewFitMode !== 'undefined' && previewFitMode !== 'default') {
+ debouncedRender();
+ }
}
});
}
diff --git a/src/styles.css b/src/styles.css
index b32fdec..554f5c1 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -68,6 +68,7 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
/* Controls */
.editor-controls { display: flex; align-items: center; gap: 6px; height: 20px; }
+.preview-controls { display: flex; align-items: center; gap: 6px; height: 20px; }
.view-label { font-size: 10px; margin-right: 4px; }
.view-toggle-group,
.dataset-toggle-group { display: flex; }
@@ -173,6 +174,11 @@ body { font-family: var(--font-main); height: 100vh; overflow: hidden; backgroun
/* Panel Content */
.panel-content { flex: 1; padding: 8px; overflow: hidden; background: var(--bg-white); border: 1px inset var(--win-gray); display: flex; flex-direction: column; color: var(--text-primary); }
+/* Preview Overlay - dims visualization during editing/resizing */
+.preview-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.25); pointer-events: none; transition: opacity 0.2s ease; z-index: 10; }
+.preview-overlay.active { opacity: 1; }
+:root[data-theme="experimental"] .preview-overlay { background: rgba(0, 0, 0, 0.4); }
+
/* Placeholder */
.placeholder { color: var(--win-gray-dark); font-style: italic; text-align: center; margin-top: 40px; font-size: 12px; }
:root[data-theme="experimental"] .placeholder { color: var(--win-gray-light); }