// chart-scaffold.js — Gemeinsame Scaffold-Funktionen für Charts
import { ensureTooltip } from 'chart-dom-utils';
/**
* Erstellt die gemeinsame Chart-Card-Struktur
* @param {HTMLElement} mount - Mount-Element
* @param {Object} options - Konfiguration
* @param {string} options.title - Chart-Titel
* @param {string} options.legendHTML - HTML für Legende
* @param {string} [options.cacheKey='__chartScaffold'] - Cache-Property-Name
* @returns {Object} Scaffold-Objekte
*/
export function ensureChartScaffold(mount, { title, legendHTML, cacheKey = '__chartScaffold' }) {
if (mount[cacheKey]) return mount[cacheKey];
mount.classList.add('card', 'mb-0');
mount.style.position = 'relative';
// Header mit Titel und Vollbild-Button
const header = document.createElement('div');
header.className = 'card-header py-2 bg-body-secondary d-flex align-items-center';
const hTitle = document.createElement('div');
hTitle.className = 'fw-semibold';
hTitle.textContent = title;
const fsBtn = document.createElement('button');
fsBtn.type = 'button';
fsBtn.className = 'btn btn-outline-secondary btn-sm ms-auto';
fsBtn.title = 'Vollbild';
fsBtn.innerHTML = '<i class="bi-arrows-fullscreen" aria-hidden="true"></i>';
header.append(hTitle, fsBtn);
// Legende
const legend = document.createElement('div');
legend.className =
'legend small text-body-secondary d-flex align-items-center gap-3 justify-content-center pt-2';
legend.innerHTML = legendHTML;
// Lücken-Legende hinzufügen falls nicht vorhanden
if (!legend.querySelector('.legend-gaps')) {
const legendGaps = document.createElement('span');
legendGaps.className = 'legend-gaps d-inline-flex align-items-center gap-1 d-none';
legendGaps.innerHTML = `
<svg class="layer-gaps" width="12" height="8" viewBox="0 0 12 8" aria-hidden="true">
<rect x="0" y="0" width="12" height="8"></rect>
</svg> Lücken
`;
legendGaps.style.display = 'none';
legend.appendChild(legendGaps);
}
// Body und Tooltip
const body = document.createElement('div');
body.className = 'chart-body p-0';
const tooltip = ensureTooltip(mount);
mount.replaceChildren(header, legend, body, tooltip);
// Fullscreen-Logik
let fsExitBtn = null;
let originalSvg = null;
let fullscreenSvg = null;
const enterFs = () => mount.requestFullscreen?.() || mount.webkitRequestFullscreen?.();
const exitFs = () => document.exitFullscreen?.() || document.webkitExitFullscreen?.();
fsBtn.addEventListener('click', () => {
const inFs = document.fullscreenElement === mount || document.webkitFullscreenElement === mount;
inFs ? exitFs() : enterFs();
});
const onFsChange = () => {
const inFs = document.fullscreenElement === mount || document.webkitFullscreenElement === mount;
if (inFs) {
// Fullscreen aktiviert
originalSvg = body.querySelector('svg');
if (originalSvg) {
fullscreenSvg = originalSvg.cloneNode(true);
fullscreenSvg.setAttribute('width', '100%');
fullscreenSvg.setAttribute('height', '100%');
fullscreenSvg.style.width = '100%';
// Höhe = 100vh minus Höhe von Header + Legende
const headerHeight = header.offsetHeight || 0;
const legendHeight = legend.offsetHeight || 0;
const totalOffset = headerHeight + legendHeight;
fullscreenSvg.style.height = `calc(100vh - ${totalOffset}px)`;
fullscreenSvg.style.display = 'block';
body.replaceChildren(fullscreenSvg);
}
mount.classList.add('fs-active');
body.style.minHeight = '100vh';
if (!fsExitBtn) {
fsExitBtn = document.createElement('button');
fsExitBtn.type = 'button';
fsExitBtn.className = 'btn btn-sm fs-exit-btn';
fsExitBtn.innerHTML = '<i class="bi-arrows-angle-contract" aria-hidden="true"></i>';
fsExitBtn.title = 'Vollbild schließen';
fsExitBtn.addEventListener('click', exitFs);
}
mount.appendChild(fsExitBtn);
} else {
// Fullscreen deaktiviert
mount.classList.remove('fs-active');
body.style.minHeight = '';
if (fsExitBtn && fsExitBtn.parentNode === mount) mount.removeChild(fsExitBtn);
if (originalSvg) {
body.replaceChildren(originalSvg);
originalSvg = null;
}
fullscreenSvg = null;
}
};
document.addEventListener('fullscreenchange', onFsChange);
document.addEventListener('webkitfullscreenchange', onFsChange);
// Cleanup function für Event-Listener
mount.__cleanupFullscreenListeners = () => {
document.removeEventListener('fullscreenchange', onFsChange);
document.removeEventListener('webkitfullscreenchange', onFsChange);
};
const legendGaps = legend.querySelector('.legend-gaps');
const scaffold = { header, hTitle, legend, legendGaps, body, tooltip, fsBtn };
mount[cacheKey] = scaffold;
return scaffold;
}