Files
einkaufsapp/web/html/js/appbar.js
2026-08-09 18:44:21 +02:00

129 lines
4.1 KiB
JavaScript

// Gemeinsame Kopfleiste für alle Ansichten.
//
// Vorher hatte jede Ansicht ihre eigene Zeile aus Textlinks. Bei sechs
// Einträgen unterschiedlicher Länge - "Drucken", "Märkte & Gruppen",
// "Teilen (2)" - brach das auf schmalen Geräten unregelmäßig um, und die
// Grundlinien lagen versetzt.
//
// Jetzt: links der Weg zurück, rechts ein Menü, dessen Einträge
// untereinander stehen. Eine einzelne Aktion wird direkt angezeigt -
// ein Menü mit einem Eintrag wäre ein Klick zu viel.
"use strict";
import { el } from "./dom.js";
let openPanel = null;
/** Schließt ein offenes Menü. Wird von aussen aufgerufen, wenn die
* Ansicht wechselt - sonst bliebe das Menü über den Wechsel hinweg
* stehen. */
export function closeMenu() {
if (!openPanel) return;
openPanel.panel.hidden = true;
openPanel.button.setAttribute("aria-expanded", "false");
document.removeEventListener("click", openPanel.onOutside, true);
document.removeEventListener("keydown", openPanel.onKey);
openPanel = null;
}
/**
* @param {object} options
* @param {{label: string, onClick?: Function}} [options.back]
* @param {string} [options.title] Kontext, etwa der Listenname
* @param {Array<{label: string, onClick?: Function, href?: string,
* danger?: boolean, hint?: string}>} [options.actions]
*/
export function appBar({ back, title, actions = [] } = {}) {
closeMenu();
const usable = actions.filter(Boolean);
const left = back
? el("button.bar-back", { type: "button", onclick: back.onClick },
"\u2039 " + back.label)
: el("span.bar-back", {});
const middle = title ? el("span.bar-title", { title }, title) : el("span", {});
function entry(action) {
const content = [
el("span.entry-label", {}, action.label),
action.hint ? el("span.entry-hint", {}, action.hint) : null,
];
// Ein Verweis bleibt ein Verweis: So funktionieren "In neuem Tab
// öffnen" und das Kopieren der Adresse wie erwartet.
const node = action.href
? el("a", {
href: action.href,
target: action.target || "_blank",
rel: "noopener",
className: action.danger ? "danger" : "",
onclick: () => closeMenu(),
}, content)
: el("button", {
type: "button",
className: action.danger ? "danger" : "",
onclick: () => { closeMenu(); action.onClick?.(); },
}, content);
return el("li", {}, node);
}
let right;
if (usable.length === 0) {
right = el("span", {});
} else if (usable.length === 1) {
const only = usable[0];
right = only.href
? el("a.bar-single", {
href: only.href, target: only.target || "_blank", rel: "noopener",
}, only.label)
: el("button.bar-single", { type: "button", onclick: only.onClick },
only.label);
} else {
const panel = el("ul.menu-panel", { hidden: true }, usable.map(entry));
const button = el("button.burger", {
type: "button",
"aria-haspopup": "true",
"aria-expanded": "false",
"aria-label": "Menü",
title: "Menü",
},
el("span.burger-lines", { "aria-hidden": "true" }),
el("span.burger-text", {}, "Menü"));
button.addEventListener("click", (ev) => {
ev.stopPropagation();
if (openPanel && openPanel.panel === panel) {
closeMenu();
return;
}
closeMenu();
panel.hidden = false;
button.setAttribute("aria-expanded", "true");
const onOutside = (event) => {
if (!panel.contains(event.target) && event.target !== button) {
closeMenu();
}
};
const onKey = (event) => {
if (event.key === "Escape") {
closeMenu();
button.focus();
}
};
// In der Erfassungsphase, damit auch Klicks auf Elemente greifen,
// die ihrerseits die Weiterleitung stoppen.
document.addEventListener("click", onOutside, true);
document.addEventListener("keydown", onKey);
openPanel = { panel, button, onOutside, onKey };
});
right = el("div.bar-menu", {}, button, panel);
}
return el("header.app-bar", {}, left, middle, right);
}