Das Dreipunktemenü in der Prüfliste ging gar nicht auf

Der öffnende Klick blubbert nach dem eigenen Zuhörer weiter zum Dokument,
und der dortige schliesst alles, was nicht im Menü liegt — also auch
dieses, im selben Augenblick. In der Mediathek stand deshalb ein
`stopPropagation()` am Griff; in der Prüfliste fehlte es.

Eine Regel, an die sich jeder Aufrufer erinnern muss, ist keine Regel.
Deshalb nicht die Kopie nachgezogen, sondern das Menü merkt sich jetzt
selbst, woran es hängt, und der globale Zuhörer lässt den Griff in Ruhe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-04 21:07:36 +02:00
co-authored by Claude Opus 5
parent d2fbaad871
commit 74c625b829
+26 -6
View File
@@ -22,7 +22,7 @@ export type Menuepunkt = {
export function kontextmenue(ziel: HTMLElement, punkte: () => Menuepunkt[]): void {
ziel.addEventListener('contextmenu', e => {
e.preventDefault();
oeffnen(e.clientX, e.clientY, punkte());
oeffnen(e.clientX, e.clientY, punkte(), ziel);
});
// Die Kontextmenü-Taste und Shift+F10 — dieselbe Geste ohne Maus.
@@ -30,7 +30,7 @@ export function kontextmenue(ziel: HTMLElement, punkte: () => Menuepunkt[]): voi
if (e.key !== 'ContextMenu' && !(e.key === 'F10' && e.shiftKey)) return;
e.preventDefault();
const kasten = ziel.getBoundingClientRect();
oeffnen(kasten.left + kasten.width / 2, kasten.top + kasten.height / 2, punkte());
oeffnen(kasten.left + kasten.width / 2, kasten.top + kasten.height / 2, punkte(), ziel);
});
// Langer Druck. 500 ms, und jede Bewegung bricht ab — sonst löst Scrollen
@@ -42,7 +42,7 @@ export function kontextmenue(ziel: HTMLElement, punkte: () => Menuepunkt[]): voi
const f = e.touches[0];
if (!f) return;
start = { x: f.clientX, y: f.clientY };
druck = window.setTimeout(() => oeffnen(start.x, start.y, punkte()), 500);
druck = window.setTimeout(() => oeffnen(start.x, start.y, punkte(), ziel), 500);
}, { passive: true });
const abbrechen = () => { if (druck !== undefined) { clearTimeout(druck); druck = undefined; } };
@@ -63,20 +63,36 @@ export function kontextmenue(ziel: HTMLElement, punkte: () => Menuepunkt[]): voi
*/
export function menueAnZiel(ziel: HTMLElement, punkte: Menuepunkt[]): void {
const kasten = ziel.getBoundingClientRect();
oeffnen(kasten.right, kasten.bottom + 4, punkte);
oeffnen(kasten.right, kasten.bottom + 4, punkte, ziel);
}
/** Das offene Menü — immer höchstens eines. */
let offen: HTMLElement | null = null;
/** Woran es hängt — der Knopf, der es geöffnet hat.
*
* **Weil der öffnende Klick noch unterwegs ist.** Er blubbert nach dem
* eigenen Zuhörer weiter zum Dokument, und der dortige schliesst alles, was
* nicht im Menü liegt — also auch dieses, im selben Augenblick. In der
* Mediathek stand deshalb ein `stopPropagation()` am Knopf; in der Prüfliste
* fehlte es, und das Menü ging gar nicht auf. Eine Regel, an die sich jeder
* Aufrufer erinnern muss, ist keine Regel. Jetzt merkt sich das Menü selbst,
* woran es hängt.
*/
let ausloeser: HTMLElement | null = null;
export function menueSchliessen(): void {
offen?.remove();
offen = null;
ausloeser = null;
}
function oeffnen(x: number, y: number, punkte: Menuepunkt[]): void {
function oeffnen(
x: number, y: number, punkte: Menuepunkt[], woran: HTMLElement | null = null,
): void {
menueSchliessen();
if (punkte.length === 0) return;
ausloeser = woran;
const menue = document.createElement('div');
menue.className = 'kontextmenue';
@@ -118,7 +134,11 @@ function oeffnen(x: number, y: number, punkte: Menuepunkt[]): void {
// registriert und nicht je Kachel — sonst hingen an vierhundert Kacheln
// zwölfhundert Zuhörer am Dokument.
document.addEventListener('click', e => {
if (offen && !offen.contains(e.target as Node)) menueSchliessen();
if (!offen) return;
const ziel = e.target as Node;
// Der Klick auf den Griff hat es gerade geöffnet — er schliesst es nicht.
if (offen.contains(ziel) || ausloeser?.contains(ziel)) return;
menueSchliessen();
});
document.addEventListener('keydown', e => { if (e.key === 'Escape') menueSchliessen(); });
window.addEventListener('scroll', menueSchliessen, { passive: true });