From 0fe4c07851893c759ddee8de332121b3b050295e Mon Sep 17 00:00:00 2001 From: mhoennig Date: Fri, 28 Aug 2026 16:43:01 +0200 Subject: [PATCH] =?UTF-8?q?feat(diagram):=20Knoten-Fenster=20=C3=B6ffnet?= =?UTF-8?q?=20nur=20noch=20auf=20Klick,=20nicht=20mehr=20bei=20Hover=20(D9?= =?UTF-8?q?2,=20SPEC=20=C2=A76/=C2=A79)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der einfache Klick toggelt das Fenster überall — wie der Tipp auf Touch (D52); der Link eines Knotens ist damit auch am Zeiger der ↗-Knopf im Fenster, Enter und Strg+Klick öffnen die URL weiterhin direkt. Tastatur- fokus öffnet nur noch echten (:focus-visible), focusout schließt nicht in das Fenster hinein, und die Fokus-Rückgabe nach dem Falten ruft das Fenster ausdrücklich nie. Co-Authored-By: Claude Fable 5 --- docs/CHANGELOG.md | 2 + docs/DECISIONS.md | 65 ++++++++++++++++++++++++++++++ docs/SPEC.md | 52 +++++++++++++----------- frontend/CLAUDE.md | 9 +++++ frontend/src/app.js | 97 +++++++++++++++++++++++---------------------- 5 files changed, 154 insertions(+), 71 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ff2c80c..0d6ceca 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,8 @@ reverse. ## 2026-08-28 +- The node window no longer opens on hover: click a node to open it, click again or elsewhere to close — folding and moving across the tree stay snappy +- A linked node's plain click now opens the window everywhere, like on touch — the URL sits behind the ↗ button, and Enter on the focused node or Ctrl+click still open it directly - The Taiga buttons in the node window sit side by side in one row now — the window grows a little wider for them, and they wrap only when space truly runs out - The open button moved into the ticket box's action row, next to write and take, and its label shrank to "Open #US-123" — the neighbours say where - "Link ticket" in the node window binds an existing Taiga story or task to a node: paste the ticket's URL — it carries project, type and number by itself — or enter a ref or bare number diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 14587d8..8082e95 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -8568,3 +8568,68 @@ längsten Labels): drei Anlege-Knöpfe eine Zeile, „→ nach Taiga schreiben Ref-Knoten ohne Projekt-Slug (keine Knöpfe) fällt auf 336px zurück — `tipwide` ist weg. 628 Tests unverändert (reine UI-Verdrahtung, Browser-geprüft — die D54-Nachtrag-3-Grenze). + +## D92 — Das Knoten-Fenster öffnet nur noch auf Klick, nicht mehr beim Überfahren +Gemeldet vom Nutzer: „Mit mehr Funktionalität in dem Popup, so dass es auch +träger geworden ist als der einfache Tooltip vorher, gibt es ein +Usability-Problem: Wenn man nur einen Knoten auf-/zuklappen will, kommt schon +das Popup." Das trifft die Sache: D57 hat dem Fenster den Hover gegeben, als +es noch ein besserer Tooltip war — Beschreibung plus Kurz-Fakten. Seit D91 +trägt es Ticket-Stand, Anlege-, Verknüpfen- und Abgleich-Knöpfe und ist +breiter geworden (`tipwide`, D91-Nachtrag 12); beim Ansteuern des Falt-Chips +stand es regelmäßig schon im Weg. Ein Fenster mit Aktionen ist kein Tooltip +mehr — es verdient eine ausdrückliche Geste. + +**Der einfache Klick öffnet, ein zweiter (oder daneben) schließt** — wörtlich +die Touch-Regel aus D52, jetzt überall. Damit fällt zwangsläufig eine +Entscheidung über die **verlinkten Knoten** (§6: der einfache Klick öffnete +dort die URL): Eine Geste kann nicht beides tun, und D25 hat ausdrücklich +abgelehnt, dass derselbe Klick je nach Knoten Verschiedenes tut. Also gilt +die D52-Abwägung jetzt auch am Zeiger — „ansehen ist die häufigere Absicht, +und der Link bleibt einen Klick entfernt": Der **↗-Knopf steht in jedem +Fenster eines verlinkten Knotens**, nicht mehr nur auf Touch. Direkt zur URL +führen weiterhin **Enter** am fokussierten Knoten (der native Weg; im +Klick-Handler an `e.detail === 0` erkannt und durchgelassen) und +**Strg+Klick** (Browser-Default, Hintergrund-Tab — auf Zeilen mit +Ticket-Referenz gewinnt das Taiga-Öffnen, D91-Nachtrag 5, wie bisher). +Alt+Klick (Sprung, D25) und der Falt-Chip (D38) sind im Handler ausgenommen +und bleiben, was sie sind. + +**Tastaturfokus öffnet weiter sofort — aber nur echten.** Das war die +verdeckte Falle des Umbaus: Auch der Mausklick fokussiert den Knoten, und der +`focusin`-Handler (D57) hätte das Fenster geöffnet, bevor der Klick-Toggle es +für „schon offen" hielte und sofort wieder schlösse — netto bliebe es zu. +Unterschieden wird mit `:focus-visible`, der Browser-eigenen Heuristik, die +D25-Nachtrag 2 für genau diese Frage schon vermessen hat (nach Mausklick +falsch, nach Tab wahr). Nebenwirkung, bewusst hingenommen: Auch der +programmatische Fokus nach Alt+Klick aus dem Text und nach dem +Stationen-Knopf (D47) folgt der Heuristik — nach einer Maus-Geste öffnet dort +kein Fenster mehr mit. Das passt zur Stoßrichtung dieses Eintrags: weniger +ungefragte Fenster. + +**Und `focusout` darf den Knopf-Klick nicht mehr töten.** Mit dem Klick ist +der Knoten beim offenen Fenster jetzt regelmäßig **fokussiert** — vorher +(Hover) war er das nicht, und der `focusout`-Schließer war harmlos. Die +Knöpfe im Fenster sind trotz `tabindex="-1"` klick-fokussierbar: mousedown +auf einen Taiga-Knopf blurte den Knoten, `focusout` schlösse das Fenster +zwischen mousedown und mouseup, und der Klick ginge an einem +`display:none`-Element verloren. Deshalb schließt `focusout` nicht, wenn +`relatedTarget` im Fenster liegt; Tab zum nächsten Knoten schließt weiter. + +**Ersatzlos weg:** die 350-ms-Öffnungs- und 120-ms-Schließ-Timer, die +Sofort-Weiterreiche bei schon offenem Fenster, `pointerenter`/`pointerleave` +am Fenster und die `finePointer`-Weiche — das Fenster ist jetzt auf allen +Geräten dieselbe Klick-Sache, und ein einmal geöffnetes bleibt stehen, bis +etwas es ausdrücklich schließt (zweiter Klick, Klick daneben, Esc, ×, +Scrollen, Neubau — die D52-Liste, unverändert). Die 400-ms-Verzögerung des +Ticket-Abrufs (D91-Nachtrag 6) bleibt: Sie schützt jetzt das Durch-Tabben +statt des Vorüberfahrens, und der Bulk-Cache (D91-Nachtrag 10) macht sie +ohnehin meist unsichtbar. + +**Der Preis, benannt:** Beim schnellen Überfliegen eines fremden Plans +zeigte der Hover die Beschreibungen ohne Klick — das ist weg, je Knoten +kostet es jetzt einen Klick. Die ”-Marke (D40) und der ↗ hinter dem Label +sagen weiterhin, wo sich einer lohnt. Und ein Doppelklick öffnet und +schließt in einem Zug — hinnehmbar, auf Knoten doppelklickt niemand +absichtlich. + diff --git a/docs/SPEC.md b/docs/SPEC.md index f0e674c..1e1a225 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -414,13 +414,16 @@ Größe als **Bereich** gelesen (Untergrenzen verdoppeln sich): ## 6. Links -- Ein nacktes `https://…`-Token macht den ganzen Knoten klickbar - (neuer Tab, `rel="noopener"`); Kennzeichnung mit ↗ hinter dem Label. -- **Ausnahme auf Touch-Geräten:** Dort öffnet der einfache Tipp nicht die URL, - sondern das Knoten-Fenster (§9) — der Link steht darin als eigener Knopf. Ohne - Zeiger gibt es keinen Tooltip, und ein einziger Tipp kann nicht beides tun; - „ansehen" ist die häufigere Absicht, und der Link bleibt einen Tipp entfernt. - Siehe D52. +- Ein nacktes `https://…`-Token verlinkt den Knoten; Kennzeichnung mit ↗ + hinter dem Label. +- Geöffnet wird die URL über den **↗-Knopf im Knoten-Fenster** (§9; neuer + Tab, `rel="noopener"`): Der einfache Klick wie der einfache Tipp öffnen das + **Fenster**, nicht die URL — eine Geste kann nicht beides tun, „ansehen" + ist die häufigere Absicht, und der Link bleibt einen Klick entfernt + (D52 für Touch, D92 für den Zeiger). **Enter** am fokussierten Knoten und + **Strg+Klick** (Browser-Default, Hintergrund-Tab) öffnen die URL weiterhin + direkt — auf Zeilen mit Ticket-Referenz gewinnt bei Strg+Klick das + Taiga-Öffnen (§9). ## 7. Personen-Tags @@ -469,11 +472,13 @@ Grafikexport: Der Text selbst kann dort nicht angezeigt werden, eine Marke ohne Ziel wäre Rauschen. Siehe D40. **Gezeigt wird das immer im eigenen Knoten-Fenster**, nicht im Tooltip des -Browsers: am Zeiger nach kurzer Verzögerung beim Überfahren, bei Tastaturfokus -sofort, auf Touch beim einfachen Tipp (§6). Ein `title` kann weder Absätze noch -eine Linie und erscheint nie beim Tastaturfokus; das Fenster kann beides. Der -Inhalt ist derselbe — die Beschreibung als Absatz, die Kurz-Fakten dahinter -durch eine echte Linie abgesetzt. Siehe D57. +Browsers: beim **einfachen Klick bzw. Tipp** auf den Knoten (ein zweiter +schließt), bei **Tastaturfokus** sofort. Beim bloßen Überfahren mit dem +Zeiger öffnet es **nicht** — das tat es zwischenzeitlich (D57) und stand +damit dem Falten und dem Bewegen über den Baum im Weg (D92). Ein `title` +kann weder Absätze noch eine Linie und erscheint nie beim Tastaturfokus; das +Fenster kann beides. Der Inhalt ist derselbe — die Beschreibung als Absatz, +die Kurz-Fakten dahinter durch eine echte Linie abgesetzt. Siehe D57, D92. - Das Fenster hängt am angetippten Knoten (kleine Spitze zu ihm hin) und weicht nach oben aus, wenn unten kein Platz ist. Der Knoten trägt, solange es offen @@ -481,13 +486,12 @@ durch eine echte Linie abgesetzt. Siehe D57. - Es schließt beim Tipp daneben, beim erneuten Tipp auf denselben Knoten, mit **Esc**, über sein ×, beim Scrollen des Diagramms und bei jedem Neubau. - Ein **verlinkter** Knoten (§6) bekommt darin einen ↗-Knopf, der die URL im - neuen Tab öffnet — auf Touch ist das der Weg zum Link. -- Der **Sprung-Hinweis** nennt hier den langen Druck statt Alt+Klick (§9, + neuen Tab öffnet — der einfache Klick/Tipp gehört dem Fenster (§6, D92). +- Der **Sprung-Hinweis** nennt auf Touch den langen Druck statt Alt+Klick (§9, Sprung zwischen Diagramm und Text) — Alt gibt es auf dem Telefon nicht. -- Reine Bedienhilfe: nicht im Grafikexport, nicht im Druck. Auf Geräten mit - Zeiger ändert sich nichts, dort bleibt es beim Tooltip. +- Reine Bedienhilfe: nicht im Grafikexport, nicht im Druck. -Siehe D52. +Siehe D52, D92. **Ticket-Stand im Knoten-Fenster (§11).** Trägt die Zeile eine Ticket-Referenz (`#US-123` / `#T-1234`) und ist ihr Teilbaum per @@ -519,8 +523,9 @@ es einen gibt, den **Zuständigen**. Farbe des Knotens ist die Rückmeldung. - Beide Richtungen betreffen **einen** Knoten; eine Sammelaktion über einen Teilbaum gibt es nicht. -- Geholt wird erst, wenn das Fenster **kurz stehen bleibt** (nicht im - Vorüberfahren), und je Ticket **einmal je Sitzung** — ein ↻-Knopf im Fenster +- Geholt wird erst, wenn das Fenster **kurz stehen bleibt** (nicht beim + Durch-Tabben von Knoten zu Knoten), und je Ticket **einmal je Sitzung** — + ein ↻-Knopf im Fenster holt neu. Ohne Anmeldung an der Instanz, ohne Projekt-Zuordnung oder ohne konfiguriertes Backend geschieht still nichts; ein Fehler steht als Zeile im Fenster. @@ -1086,8 +1091,9 @@ verknüpft (siehe D25): freistehend — dort behält Strg+Klick den Sprung zur Zeile; die D67-Ausschlüsse (Kommentar, Beschreibungsteil, URL) gelten auch hier. Siehe D91-Nachtrag 5. -- Der **einfache** Klick bleibt unverändert der Link (§6): Ein Knoten mit URL - öffnet sie weiterhin im neuen Tab. Deshalb Alt und nicht der einfache Klick. +- Der **einfache** Klick gehört dem Knoten-Fenster (§6/§9, D92): Er öffnet die + Beschreibung, nicht die Zeile im Text. Deshalb Alt und nicht der einfache + Klick. - **Auffindbarkeit:** Solange **Alt gedrückt** ist, zeigen alle Knoten den Sprung-Cursor und der Knoten unter dem Zeiger einen Petrol-Ring — die Geste ist im Moment des Ausprobierens sichtbar, auch auf verlinkten Knoten. Dazu @@ -1207,8 +1213,8 @@ Label: **▾** offen, **„▸ n“** eingeklappt (n = Zahl der verborgenen Knot als kleiner **gerahmter Chip**, damit das Klickziel zu treffen ist (D64; das nackte Glyph war zu klein). Klick auf das Zeichen klappt um — der einfache Klick auf den Knoten selbst -bleibt der Link (§6); Tastatur: **←** klappt zu, **→** klappt auf am -fokussierten Knoten (WAI-ARIA-Baum-Idiom). +öffnet das Knoten-Fenster (§9, D92); Tastatur: **←** klappt zu, **→** klappt +auf am fokussierten Knoten (WAI-ARIA-Baum-Idiom). - **Anfangszustand aus dem Text (§1):** `>` klappt ein. `<` holt seinen Teilbaum hervor, indem die Faltung die Pfad-Ebenen **hinunterwandert**: diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 06fee74..2b4042e 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -654,6 +654,15 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der Es schließt bei allem, was seine Aussage hinfällig macht — Tipp daneben, zweiter Tipp, Esc, ×, Diagramm-Scroll, Bereichswechsel, Sprung in den Text, `render()`. Die letzten drei sind beim Bauen zuerst vergessen worden. +- **Das Knoten-Fenster öffnet auf Klick, nie auf Hover (D92):** Der einfache + Klick toggelt es überall (auch auf Link-Knoten — die URL ist der ↗-Knopf im + Fenster; Enter kommt als Klick mit `e.detail === 0` und wird durchgelassen). + Zwei Fallen: `focusin` öffnet nur bei `:focus-visible` — der Mausklick + fokussiert den Knoten ebenfalls, und ohne die Weiche schlösse der + Klick-Toggle das vom focusin geöffnete Fenster sofort wieder. Und + `focusout` schließt nicht, wenn `relatedTarget` im Fenster liegt — die + Knöpfe darin sind trotz `tabindex="-1"` klick-fokussierbar, sonst stirbt + der Knopf-Klick zwischen mousedown und mouseup. - **Taiga-Ticket-Anlage (D91):** Die entscheidbaren Regeln liegen headless in `taiga.js` (`ticketRefOf` = Idempotenz-Marker, `taskCandidates` = Dialog-Vorbelegung, `storyAncestor` = der nächste Vorfahr mit Story-Ref, diff --git a/frontend/src/app.js b/frontend/src/app.js index 9434dac..a1c0a2a 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1462,7 +1462,18 @@ function applyFoldPreset(mode){ /* Faltung umklappen (SPEC §9, D38). Gelingt das Zurückschreiben, ist der Text der Zustand — die Überlagerungen werden dann geleert, damit sie ihn nicht maskieren können. Nach dem Neubau bekommt derselbe Knoten den Fokus zurück, - sonst risse die Tastaturbedienung ab (das alte Element ist weg). */ + sonst risse die Tastaturbedienung ab (das alte Element ist weg) — aber ohne + das Knoten-Fenster zu rufen: Falten ist genau die Geste, der das Fenster im + Weg stand (D92), und die `:focus-visible`-Heuristik ist beim + programmatischen Fokus nicht verlässlich genug für diese Zusage. */ +let suppressTipFocus = false; +function refocusNode(line){ + const again = out.querySelector('.node[data-line="' + line + '"]'); + if(!again) return; + suppressTipFocus = true; /* focusin läuft synchron im focus() */ + again.focus({preventScroll: true}); + suppressTipFocus = false; +} function toggleFold(el){ const line = +el.dataset.line; const st = foldByLine.get(line); @@ -1473,16 +1484,14 @@ function toggleFold(el){ if(lens){ lensOverrides.set(st.key, !st.collapsed); render(); - const again2 = out.querySelector('.node[data-line="' + line + '"]'); - if(again2) again2.focus({preventScroll: true}); + refocusNode(line); return; } foldOverrides.set(st.key, !st.collapsed); /* Das Schreiben löst per `input`-Ereignis schon ein render() aus. */ if(writeFoldToText(line, !st.collapsed)) foldOverrides.clear(); else render(); - const again = out.querySelector('.node[data-line="' + line + '"]'); - if(again) again.focus({preventScroll: true}); + refocusNode(line); } /* Klick auf das Falt-Zeichen ▾/▸ klappt um. preventDefault, weil das Zeichen @@ -1577,7 +1586,7 @@ out.addEventListener('touchend', e => { `preventDefault()` öffnete ein Link-Knoten zusätzlich seine URL — auf Touch ist der Link stattdessen ein Knopf IM Fenster. */ e.preventDefault(); - toggleNodeTip(tapped); + toggleNodeTip(tapped, true); }, {passive: false}); out.addEventListener('touchcancel', disarmPress); out.addEventListener('contextmenu', e => { if(pressTimer || armedEl) e.preventDefault(); }); @@ -1605,14 +1614,14 @@ function closeNodeTip(){ nodeTip.hidden = true; } -function toggleNodeTip(el){ +function toggleNodeTip(el, touch){ if(tipNode === el){ closeNodeTip(); return; } /* zweiter Tipp schließt */ - showNodeTip(el, true); + showNodeTip(el, touch); } -/* `touch` unterscheidet die beiden Anlässe: Am Zeiger ist der ganze Knoten der - Link (§6), ein ↗-Knopf wäre dort ein zweiter Weg zum selben Ziel; und der - Sprung-Hinweis nennt Alt+Klick, den es auf dem Telefon nicht gibt. */ +/* `touch` wählt nur noch den Sprung-Hinweis (langer Druck statt Alt+Klick). + Der ↗-Knopf steht seit D92 an JEDEM verlinkten Knoten: Der einfache Klick + öffnet überall das Fenster, nicht mehr die URL — wie der Tipp auf Touch. */ function showNodeTip(el, touch){ closeNodeTip(); const title = el.getAttribute('data-tip') || ''; @@ -1621,7 +1630,7 @@ function showNodeTip(el, touch){ const desc = i >= 0 ? title.slice(0, i) : ''; const roh = i >= 0 ? title.slice(i + sep.length) : title; const facts = touch ? roh.replace(t('jumpHint'), t('jumpHintTouch')) : roh; - const href = touch && el.tagName === 'A' ? el.getAttribute('href') : null; + const href = el.tagName === 'A' ? el.getAttribute('href') : null; /* Absätze: Leerzeilen trennen (SPEC §1), einzelne Zeilenumbrüche sind bloß der Umbruch der Quelldatei und werden zu Leerzeichen. Der `title` kann das nicht — er zeigt die harten Umbrüche und sah im schmalen Fenster @@ -1661,49 +1670,41 @@ function placeNodeTip(el){ Math.max(12, Math.min(anchorX - left, w - 12)).toFixed(1) + 'px'); } -/* ---------- Dasselbe Fenster am Zeiger und an der Tastatur (D57) ---------- +/* ---------- Dasselbe Fenster am Klick und an der Tastatur (D57, D92) ---------- Es löst den nativen `title` ab: Der konnte weder Absätze noch eine Linie (der Trennstrich war aus 24 `─` gemalt, D40-Nachtrag) und erschien nie beim - Tastaturfokus. Beides kann das Fenster ohnehin schon — es wurde nur für - Touch gebaut (D52). - - Die Verzögerung ist Absicht-Erkennung: Über einen dichten Baum fährt man - hinweg, ohne etwas wissen zu wollen. Beim Wechsel von Knoten zu Knoten - zeigt es sofort weiter — wer schon liest, wartet nicht noch einmal. */ -const TIP_DELAY = 350; -let tipTimer = null, tipLeave = null; -const finePointer = () => window.matchMedia('(hover:hover) and (pointer:fine)').matches; -function cancelTipTimers(){ - clearTimeout(tipTimer); clearTimeout(tipLeave); - tipTimer = tipLeave = null; + Tastaturfokus. Der Hover-Weg aus D57 ist ausgebaut (D92): Mit den + Taiga-Aktionen wurde das Fenster gewichtiger, und beim bloßen Ansteuern des + Falt-Chips stand es schon im Weg. Der einfache Klick öffnet es jetzt + überall — wie der Tipp auf Touch (D52); der Link eines Knotens ist damit + auch am Zeiger der ↗-Knopf IM Fenster (Strg+Klick und Enter behalten den + Browser-Default und öffnen die URL direkt). */ +out.addEventListener('click', e => { + if(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return; + if(e.detail === 0) return; /* Enter am Link-Knoten bleibt der Link (D25) */ + if(e.target && e.target.closest && e.target.closest('.fold')) return; + const el = nodeFromEvent(e); + if(!el) return; + e.preventDefault(); /* der Klick gehört dem Fenster, nicht der URL */ + toggleNodeTip(el, false); +}); +/* Tastaturfokus öffnet weiter sofort (D57) — aber nur echten + (`:focus-visible`): Auch der Mausklick fokussiert den Knoten, und dann + entschiede sonst der focusin statt des Klick-Handlers — der Toggle darüber + schlösse das eben geöffnete Fenster sofort wieder. */ +function kbFocus(el){ + try{ return el.matches(':focus-visible'); }catch(_){ return false; } } -out.addEventListener('pointerover', e => { - if(e.pointerType === 'touch' || !finePointer()) return; - const el = e.target.closest && e.target.closest('.node'); - if(!el || el === tipNode) return; - cancelTipTimers(); - /* Steht schon eines offen, ist die Absicht erwiesen — dann ohne Warten. */ - tipTimer = setTimeout(() => showNodeTip(el, false), tipNode ? 0 : TIP_DELAY); -}); -out.addEventListener('pointerout', e => { - if(e.pointerType === 'touch' || !finePointer()) return; - clearTimeout(tipTimer); - /* Nicht sofort zumachen: Der Weg ins Fenster führt über den Zwischenraum, - und dort ist der Zeiger kurz über keinem von beiden. */ - clearTimeout(tipLeave); - tipLeave = setTimeout(() => { - if(!nodeTip.matches(':hover')) closeNodeTip(); - }, 120); -}); -nodeTip.addEventListener('pointerenter', cancelTipTimers); -nodeTip.addEventListener('pointerleave', () => { closeNodeTip(); }); -/* Tastaturfokus: ohne Verzögerung — er ist bereits die ausdrückliche Absicht, - und ein `title` hat hier noch nie etwas gezeigt. */ out.addEventListener('focusin', e => { + if(suppressTipFocus) return; /* Fokus-Rückgabe nach dem Falten (D38/D92) */ const el = e.target.closest && e.target.closest('.node'); - if(el && el !== tipNode) showNodeTip(el, false); + if(el && el !== tipNode && kbFocus(el)) showNodeTip(el, false); }); out.addEventListener('focusout', e => { + /* Fokus INS Fenster (die Knöpfe darin sind klick-fokussierbar) schließt + nicht — sonst verschwände der Knopf zwischen mousedown und mouseup und + sein Klick ginge verloren. */ + if(e.relatedTarget && nodeTip.contains(e.relatedTarget)) return; const el = e.target.closest && e.target.closest('.node'); if(el && el === tipNode) closeNodeTip(); });