/* Kundenansicht neu: Etage 2. Eine Unterseite (Funktion, Detailseite, Passwort …) schiebt sich als Blatt über die Hauptseite. Handy: Die Zeile unten fährt eine Etage hoch und zeigt „Zurück“ und, falls die Seite Reiter meldet, deren Kurzlabels als Balken. Desktop: Brotkrume statt Zeile, Reiter als Unterstrich im Slot unter der Überschrift. Der Inhalt meldet Reiter und Titel ohne Import per CustomEvent (bubbles): kn:reiter { eintraege, aktiv, bei } (leere Liste = keine Reiter) und kn:titel { text }. Alles Sichtbare wird mit h() und textContent gebaut. */ import { h, leeren, text } from './dom.js' import { baueReiter } from './reiter.js' const gleicheEintraege = (a, b) => a.length === b.length && a.every((x, i) => x.key === b[i].key && x.label === b[i].label && (x.kurz ?? '') === (b[i].kurz ?? '')) export function baueEtage({ blatt, haupt, zeile, titel, brot, reiterSlot, istHandy, onZurueck }) { const stand = { offen: false, titel: '', eltern: null, seite: '', reiter: null, chatPunkt: false, reiterEl: null, zurueckEl: null } const zurueckLabel = () => `Zurück zu ${stand.eltern.label}${stand.chatPunkt ? ', neue Nachricht im Chat' : ''}` function baueZurueck(voll) { return h('button', { class: voll ? 'kn-zurueck kn-zurueck--voll' : 'kn-zurueck', type: 'button', 'aria-label': zurueckLabel(), on: { click: () => onZurueck() } }, h('span', { class: 'kn-l' }, 'Zurück', stand.chatPunkt ? h('span', { class: 'kn-punkt' }) : null)) } function neuerReiter(art) { const { eintraege, aktiv } = stand.reiter // Handy zeigt die Kurzlabels (der Balken hat wenig Platz), Desktop die vollen Beschriftungen const zeigen = art === 'balken' ? eintraege : eintraege.map(({ kurz, ...rest }) => rest) return baueReiter({ eintraege: zeigen, aktiv, art, ariaLabel: 'Reiter', bei: (key) => stand.reiter?.bei?.(key) }) } function zeichneBrot() { leeren(brot) if (!stand.offen) return const verlassen = (ev) => { if (ev.defaultPrevented || ev.button > 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return ev.preventDefault() // die Brotkrume führt wie „Zurück“ zur Elternseite, ohne neuen Verlaufseintrag onZurueck() } brot.append(h('a', { href: stand.eltern.hash, on: { click: verlassen } }, stand.eltern.label), h('span', { class: 'kn-brot-trenner', 'aria-hidden': 'true' }, ' / '), h('span', { 'aria-current': 'page' }, stand.titel)) } // Zeile (Handy) und Reiter-Slot (Desktop) aus dem Stand neu aufbauen function zeichne() { leeren(zeile); leeren(reiterSlot) stand.zurueckEl = null; stand.reiterEl = null zeichneBrot() if (!stand.offen) return if (istHandy()) { stand.zurueckEl = baueZurueck(!stand.reiter) zeile.append(stand.zurueckEl) if (stand.reiter) { stand.reiterEl = neuerReiter('balken') zeile.append(h('span', { class: 'kn-vl' }), stand.reiterEl) } } else if (stand.reiter) { stand.reiterEl = neuerReiter('unterstrich') reiterSlot.append(stand.reiterEl) } stand.reiterEl?.messen() } function oeffne({ titel: titelText, eltern, seite }) { const warOffen = stand.offen Object.assign(stand, { offen: true, titel: titelText, eltern, seite, reiter: null }) text(titel, titelText) for (const s of blatt.querySelectorAll('section[data-etage]')) s.hidden = s.getAttribute('data-etage') !== seite haupt.setAttribute('inert', '') if (!warOffen) { blatt.hidden = false void blatt.offsetWidth // Ausgangslage festschreiben, damit das Blatt von unten einfährt blatt.classList.add('kn-blatt--auf') } zeichne() } function schliesse() { stand.offen = false stand.reiter = null blatt.classList.remove('kn-blatt--auf') blatt.hidden = true haupt.removeAttribute('inert') zeichne() } function setzeReiter({ eintraege = [], aktiv = '', bei } = {}) { if (!stand.offen) return const alt = stand.reiter stand.reiter = eintraege.length ? { eintraege, aktiv, bei } : null // Gleiche Reiter bleiben stehen (der Balken fährt weiter), nur die Auswahl wechselt if (alt && stand.reiter && stand.reiterEl && gleicheEintraege(alt.eintraege, eintraege)) stand.reiterEl.setzeAktiv(aktiv) else zeichne() } function setzeChatPunkt(an) { stand.chatPunkt = Boolean(an) const knopf = stand.zurueckEl if (!knopf) return const wort = knopf.querySelector('.kn-l') const punkt = wort.querySelector('.kn-punkt') if (stand.chatPunkt && !punkt) wort.append(h('span', { class: 'kn-punkt' })) else if (!stand.chatPunkt && punkt) punkt.remove() knopf.setAttribute('aria-label', zurueckLabel()) } // Meldungen aus dem Inhalt; Meldungen einer verborgenen Seite oder nach dem Schließen gelten nicht const vonDieserSeite = (ev) => { const s = ev.target?.closest?.('section[data-etage]') return stand.offen && (!s || s.getAttribute('data-etage') === stand.seite) } blatt.addEventListener('kn:reiter', (ev) => { if (vonDieserSeite(ev)) setzeReiter(ev.detail ?? {}) }) blatt.addEventListener('kn:titel', (ev) => { if (!vonDieserSeite(ev) || !ev.detail?.text) return stand.titel = String(ev.detail.text) text(titel, stand.titel) zeichneBrot() }) return { oeffne, schliesse, setzeReiter, neuZeichnen: zeichne, setzeChatPunkt, istOffen: () => stand.offen } }