/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ window.Ribbon = (() => { const sources = {}; const SOURCE_ORDER = ['app', 'editor']; let root = null, tabsEl = null, bodyEl = null; let activeKey = null, lastPlainKey = null; let auto = true; let collapsed = false, notified = null; const listeners = []; const PREF_KEY = 'tectum.ribbon.collapsed'; const icon = (n) => ``; const big = (label, iconName, attrs, cls) => ``; const small = (label, iconName, attrs, cls) => ``; const kv = (label, value, unit) => `
${label}${value}${unit ? `${unit}` : ''}
`; function allTabs() { const byKey = new Map(); SOURCE_ORDER.concat(Object.keys(sources).filter(n => !SOURCE_ORDER.includes(n))).forEach(name => { (sources[name] || []).forEach(t => { if (!byKey.has(t.key)) byKey.set(t.key, { key: t.key, label: t.label, order: t.order || 0, contextual: !!t.contextual, icon: t.icon, parts: [] }); const m = byKey.get(t.key); if (t.order !== undefined && name !== 'app') m.order = t.order; if (t.label && name !== 'app') m.label = t.label; if (t.contextual) m.contextual = true; m.parts.push(t); }); }); return Array.from(byKey.values()).sort((a, b) => (a.contextual - b.contextual) || (a.order - b.order)); } function renderTabs(list) { tabsEl.innerHTML = list.map(t => ``).join(''); } function renderBody(list) { const tab = list.find(t => t.key === activeKey); if (!tab) { bodyEl.innerHTML = ''; return; } const groups = []; tab.parts.forEach(p => (p.groups ? p.groups() : []).forEach(g => g && groups.push(g))); bodyEl.innerHTML = groups.map(g => `
${g.html}
${g.label}
`).join('') + ``; tab.parts.forEach(p => p.wire && p.wire(bodyEl)); } function render() { if (!root) return; const list = allTabs(); if (!list.some(t => t.key === activeKey) || (auto && !list.some(t => t.key === activeKey && t.contextual))) { const pick = [lastPlainKey, 'start'].find(k => list.some(t => t.key === k)); activeKey = pick || (list.find(t => !t.contextual) || {}).key || null; } renderTabs(list); renderBody(list); root.classList.toggle('rb-collapsed', collapsed); if (notified !== activeKey) { notified = activeKey; listeners.forEach(fn => fn(activeKey)); } } function activate(key) { const list = allTabs(); const tab = list.find(t => t.key === key); if (!tab) return; activeKey = key; auto = false; if (!tab.contextual) lastPlainKey = key; if (collapsed) { collapsed = false; savePref(); } render(); tab.parts.forEach(p => p.onShow && p.onShow()); } function savePref() { try { localStorage.setItem(PREF_KEY, collapsed ? '1' : '0'); } catch (e) { } } function init(el) { root = el; try { collapsed = localStorage.getItem(PREF_KEY) === '1'; } catch (e) { collapsed = false; } tabsEl = el.querySelector('.rb-tabs'); bodyEl = el.querySelector('.rb-body'); tabsEl.addEventListener('click', (e) => { const b = e.target.closest('[data-rb-tab]'); if (!b) return; if (b.dataset.rbTab === activeKey && collapsed) { collapsed = false; savePref(); render(); return; } activate(b.dataset.rbTab); }); tabsEl.addEventListener('dblclick', (e) => { if (!e.target.closest('[data-rb-tab]')) return; collapsed = !collapsed; savePref(); render(); }); bodyEl.addEventListener('click', (e) => { if (e.target.closest('.rb-collapse')) { collapsed = true; savePref(); render(); } }); render(); } function setSource(name, tabs, opts) { sources[name] = tabs || []; const ctx = (tabs || []).find(t => t.contextual); if (opts && opts.focus && ctx) { activeKey = ctx.key; if (collapsed) { collapsed = false; savePref(); } } render(); if (opts && opts.focus && ctx && ctx.onShow) ctx.onShow(); } function onChange(fn) { listeners.push(fn); return () => { const i = listeners.indexOf(fn); if (i >= 0) listeners.splice(i, 1); }; } return { init, setSource, activate, refresh: render, active: () => activeKey, onChange, big, small, kv }; })();