feat(calculator): Tab-System mit registerMode() und switchMode()

- Neue Eigenschaften: _modes (Map), _activeMode, _tabBarEl
- registerMode() für externe Mode-Dateien, aktualisiert Tab-Bar live
- renderBody() baut jetzt Tab-Bar + .calc-mode-body Container
- _renderTabBar(), _updateTabBar(), switchMode() implementiert
- _renderStandardMode() extrahiert aus altem renderBody()
- save()/load(): activeMode wird persistiert und wiederhergestellt
- onClose(): aktiven Modus sauber aufräumen, _tabBarEl zurücksetzen
- CSS: .calc-tab-bar, .calc-tab, .calc-tab-icon, .calc-tab-label, .calc-mode-body
- i18n: calculator.tab.standard (DE + EN)
This commit is contained in:
2026-04-16 21:43:03 +02:00
parent 7be391de99
commit 2487ac772f
3 changed files with 209 additions and 1 deletions
+50
View File
@@ -1282,6 +1282,56 @@ body.show-desc .bm-desc { display: block; }
font-weight: 600; font-weight: 600;
} }
/* Calculator Tab System */
.calc-tab-bar {
display: flex;
background: rgba(0,0,0,0.2);
border-bottom: 1px solid var(--border);
overflow-x: auto;
scrollbar-width: none;
flex-shrink: 0;
}
.calc-tab-bar::-webkit-scrollbar {
display: none;
}
.calc-tab {
display: flex;
align-items: center;
gap: 3px;
padding: 6px 8px;
border: none;
border-bottom: 2px solid transparent;
background: none;
color: var(--text-muted);
font-size: 11px;
font-family: 'Rajdhani', sans-serif;
cursor: pointer;
white-space: nowrap;
transition: color 0.15s, border-color 0.15s;
flex-shrink: 0;
}
.calc-tab:hover {
color: var(--text-secondary);
}
.calc-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
font-weight: 600;
}
.calc-tab-icon {
font-size: 12px;
}
.calc-tab-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.calc-mode-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* ============================================ /* ============================================
TIMER WIDGET TIMER WIDGET
============================================ */ ============================================ */
+157 -1
View File
@@ -17,6 +17,22 @@ const Calculator = {
_displayExprEl: null, _displayExprEl: null,
_displayResultEl: null, _displayResultEl: null,
_keydownHandler: null, _keydownHandler: null,
_modes: new Map(),
_activeMode: 'standard',
_tabBarEl: null,
// ---- MODE REGISTRY ----
/**
* Modus registrieren (wird von externen Mode-Dateien aufgerufen)
* @param {string} name - Eindeutiger Modus-Name
* @param {Object} config - { label, shortName, titleKey, render(bodyEl), destroy() }
*/
registerMode(name, config) {
this._modes.set(name, config);
// Tab-Bar aktualisieren falls Widget bereits offen
if (this._tabBarEl) this._renderTabBar();
},
// ---- STORAGE ---- // ---- STORAGE ----
@@ -27,6 +43,9 @@ const Calculator = {
const data = await Store.get(this.STORAGE_KEY); const data = await Store.get(this.STORAGE_KEY);
if (data && data.calculator) { if (data && data.calculator) {
this._history = Array.isArray(data.calculator.history) ? data.calculator.history : []; this._history = Array.isArray(data.calculator.history) ? data.calculator.history : [];
if (data.calculator.activeMode) {
this._activeMode = data.calculator.activeMode;
}
} }
}, },
@@ -46,6 +65,7 @@ const Calculator = {
width: widgetState ? widgetState.width : 280, width: widgetState ? widgetState.width : 280,
height: widgetState ? widgetState.height : 400, height: widgetState ? widgetState.height : 400,
open: this._isOpen, open: this._isOpen,
activeMode: this._activeMode,
history: this._history.slice(0, this.MAX_HISTORY) history: this._history.slice(0, this.MAX_HISTORY)
}; };
@@ -113,8 +133,13 @@ const Calculator = {
* Wird aufgerufen wenn Widget geschlossen wird * Wird aufgerufen wenn Widget geschlossen wird
*/ */
async onClose() { async onClose() {
// Aktiven Modus aufräumen
const mode = this._modes.get(this._activeMode);
if (mode && mode.destroy) mode.destroy();
this._isOpen = false; this._isOpen = false;
this._unbindKeyboard(); this._unbindKeyboard();
this._tabBarEl = null;
this._displayExprEl = null; this._displayExprEl = null;
this._displayResultEl = null; this._displayResultEl = null;
await this.save(); await this.save();
@@ -123,14 +148,133 @@ const Calculator = {
// ---- UI RENDERING ---- // ---- UI RENDERING ----
/** /**
* Calculator-Body rendern (in Widget-Body einfuegen) * Calculator-Body rendern: Tab-Bar + aktiver Modus
* @param {HTMLElement} bodyEl * @param {HTMLElement} bodyEl
*/ */
renderBody(bodyEl) { renderBody(bodyEl) {
bodyEl.textContent = ''; bodyEl.textContent = '';
bodyEl.style.padding = '0';
bodyEl.style.display = 'flex';
bodyEl.style.flexDirection = 'column';
bodyEl.style.height = '100%';
// Tab-Bar
const tabBar = document.createElement('div');
tabBar.className = 'calc-tab-bar';
this._tabBarEl = tabBar;
this._renderTabBar();
// Mode-Body Container
const modeBody = document.createElement('div');
modeBody.className = 'calc-mode-body';
bodyEl.append(tabBar, modeBody);
// Aktiven Modus rendern
const mode = this._modes.get(this._activeMode);
if (mode) {
mode.render(modeBody);
}
},
/**
* Tab-Bar mit Buttons aus _modes Map befüllen
*/
_renderTabBar() {
if (!this._tabBarEl) return;
while (this._tabBarEl.firstChild) {
this._tabBarEl.removeChild(this._tabBarEl.firstChild);
}
this._modes.forEach((config, name) => {
const tab = document.createElement('button');
tab.type = 'button';
tab.className = 'calc-tab' + (name === this._activeMode ? ' active' : '');
tab.dataset.mode = name;
const icon = document.createElement('span');
icon.className = 'calc-tab-icon';
icon.textContent = config.label;
const label = document.createElement('span');
label.className = 'calc-tab-label';
label.textContent = config.shortName;
tab.append(icon, label);
tab.addEventListener('click', () => this.switchMode(name));
this._tabBarEl.appendChild(tab);
});
},
/**
* Aktiven Tab visuell markieren (ohne Neuaufbau)
*/
_updateTabBar() {
if (!this._tabBarEl) return;
const tabs = this._tabBarEl.querySelectorAll('.calc-tab');
tabs.forEach(tab => {
tab.classList.toggle('active', tab.dataset.mode === this._activeMode);
});
},
/**
* Modus wechseln
* @param {string} name - Ziel-Modus
*/
async switchMode(name) {
if (name === this._activeMode) return;
const mode = this._modes.get(name);
if (!mode) return;
// Alten Modus aufräumen
const oldMode = this._modes.get(this._activeMode);
if (oldMode && oldMode.destroy) oldMode.destroy();
this._activeMode = name;
// Mode-Body leeren und neu rendern
const entry = WidgetManager._widgets.get(this.WIDGET_ID);
if (!entry) return;
const modeBody = entry.el.querySelector('.calc-mode-body');
if (!modeBody) return;
modeBody.textContent = '';
mode.render(modeBody);
// Tab-UI aktualisieren
this._updateTabBar();
// Auto-Resize für komplexe Modi
const isComplex = name !== 'standard';
if (isComplex) {
const state = WidgetManager.getState(this.WIDGET_ID);
if (state) {
const newW = Math.max(state.width, 320);
const newH = Math.max(state.height, 480);
if (newW !== state.width || newH !== state.height) {
WidgetManager.resize(this.WIDGET_ID, newW, newH);
}
}
}
// Keyboard neu binden
this._unbindKeyboard();
if (name === 'standard' || name === 'scientific') {
if (entry) this._bindKeyboard(entry.el);
}
await this.save();
},
/**
* Standard-Modus UI rendern
* @param {HTMLElement} bodyEl
*/
_renderStandardMode(bodyEl) {
bodyEl.style.padding = '8px'; bodyEl.style.padding = '8px';
bodyEl.style.display = 'flex'; bodyEl.style.display = 'flex';
bodyEl.style.flexDirection = 'column'; bodyEl.style.flexDirection = 'column';
bodyEl.style.flex = '1';
bodyEl.style.overflow = 'hidden';
// Display // Display
const display = document.createElement('div'); const display = document.createElement('div');
@@ -716,5 +860,17 @@ const Calculator = {
self.save(); self.save();
} }
}); });
// Standard-Modus intern registrieren
this._modes.set('standard', {
label: '🔢',
shortName: 'Std',
titleKey: 'calculator.tab.standard',
render: (bodyEl) => this._renderStandardMode(bodyEl),
destroy: () => {
this._displayExprEl = null;
this._displayResultEl = null;
}
});
} }
}; };
+2
View File
@@ -83,6 +83,7 @@ const STRINGS = {
'calculator.title': 'Taschenrechner', 'calculator.title': 'Taschenrechner',
'calculator.history': 'History', 'calculator.history': 'History',
'calculator.error': 'Fehler', 'calculator.error': 'Fehler',
'calculator.tab.standard': 'Standard',
// Timer // Timer
'timer.title': 'Timer', 'timer.title': 'Timer',
@@ -391,6 +392,7 @@ const STRINGS = {
'calculator.title': 'Calculator', 'calculator.title': 'Calculator',
'calculator.history': 'History', 'calculator.history': 'History',
'calculator.error': 'Error', 'calculator.error': 'Error',
'calculator.tab.standard': 'Standard',
// Timer // Timer
'timer.title': 'Timer', 'timer.title': 'Timer',