fix(diagram): umgeklappter Knoten behält seine Position im Bild — Scrollstand zieht nach (D38-Nachtrag 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0fe4c07851
commit
aa6ca85bdf
@@ -19,6 +19,7 @@ reverse.
|
|||||||
|
|
||||||
## 2026-08-28
|
## 2026-08-28
|
||||||
|
|
||||||
|
- Folding or unfolding a node keeps it in place on screen now: the scroll position follows the layout shift, so the clicked node no longer jumps away
|
||||||
- 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
|
- 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
|
- 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 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
|
||||||
|
|||||||
@@ -2695,6 +2695,46 @@ eingeklappte Elternknoten trägt Ring und Puls; Alt+Klick fokussiert ihn
|
|||||||
ausgeblendeten verworfenen Zeile und ihrem Kind → weiterhin keine
|
ausgeblendeten verworfenen Zeile und ihrem Kind → weiterhin keine
|
||||||
Hervorhebung. 377 Tests, davon 5 neue in `tests/fold.test.js`.
|
Hervorhebung. 377 Tests, davon 5 neue in `tests/fold.test.js`.
|
||||||
|
|
||||||
|
**Nachtrag 5 — der umgeklappte Knoten behält seine Position im Bild
|
||||||
|
(2026-08-28).** Gemeldet als Frage: „Ist es hinzubekommen, dass beim
|
||||||
|
Auf-/Zuklappen eines Knotens dieser seine Position auf dem Canvas
|
||||||
|
beibehält?" Der Sprung hatte eine einfache Ursache: `toggleFold` baut den
|
||||||
|
Baum neu, und im horizontalen Fächer steht ein Elternknoten **zentriert über
|
||||||
|
seinem Teilbaum** — klappt der zu, schrumpft die Zelle und der Knoten rückt
|
||||||
|
um die halbe Breitendifferenz nach links, während der Scrollstand von
|
||||||
|
`.diagram` stehen bleibt. Gemessen am Testbaum: **421 px** Versatz für ein
|
||||||
|
Paket mit vier M-Kindern — der geklickte Knoten war nach dem Klick woanders.
|
||||||
|
|
||||||
|
**Der Anker: vorher messen, nachher nachziehen.** `toggleFold` hält vor dem
|
||||||
|
Neubau das `getBoundingClientRect()` des Knotens fest; `refocusNode` — die
|
||||||
|
Stelle, die nach dem Neubau ohnehin denselben Knoten wiederfindet (per
|
||||||
|
`data-line`, das Falten ändert keine Zeilennummern) — misst das neue Rect
|
||||||
|
und verschiebt `scrollLeft`/`scrollTop` von `.diagram` um die Differenz.
|
||||||
|
Beide Rects sind Viewport-Koordinaten des **ungezoomten** Scroll-Containers
|
||||||
|
(der `zoom` sitzt auf `#out` darin, D17-Nachtrag 2) — die Differenz ist
|
||||||
|
also direkt scrollbar, ohne Zoom-Rückrechnung; nachgemessen bei Zoom 0,8:
|
||||||
|
1 px Abweichung (Rundung).
|
||||||
|
|
||||||
|
**Am Rand klemmt der Browser — bestmöglich statt exakt.** Steht das
|
||||||
|
Diagramm ganz links (`scrollLeft` 0) und der Knoten will nach der Korrektur
|
||||||
|
weiter links stehen, gibt es nichts zu scrollen: Der Inhalt ist physisch
|
||||||
|
gerückt, der Knoten springt dann doch (gemessen: −421 px bei Klemme auf 0).
|
||||||
|
Das ist der ehrliche Rest — eine Gegenkorrektur per `transform` o. Ä. wäre
|
||||||
|
eine zweite Positionswahrheit neben dem Layout.
|
||||||
|
|
||||||
|
**Gilt für alle Wege durch `toggleFold`:** Falt-Chip, Tastatur ←/→ und die
|
||||||
|
Hand-Faltung unter der Personen-Linse (D87). Der **Falt-Durchschalter**
|
||||||
|
(D75) bleibt außen vor — er baut den ganzen Baum um, es gibt keinen einen
|
||||||
|
Knoten, an dem man ankern könnte.
|
||||||
|
|
||||||
|
**Nachgemessen** im Browser (Testbaum 31 Knoten, 6560 px breit in 1183 px
|
||||||
|
Sichtfenster): Zu- und Aufklappen von „Paket 3" mittig im Bild → dx 0 px,
|
||||||
|
dy 1 px über die Rundreise; die Korrektur bewegte den Scrollstand um exakt
|
||||||
|
die 421 px des Versatzes; Klemm-Fall wie oben; Zoom-Gegenprobe 0,8 → 1 px.
|
||||||
|
Der Falt-Chip-Klick öffnet weiterhin kein Knoten-Fenster (D92). 628 Tests
|
||||||
|
unverändert — die Korrektur ist DOM-Geometrie und damit Browser-geprüft
|
||||||
|
(dieselbe Grenze wie `alignStems()`, D29).
|
||||||
|
|
||||||
## D39 — Effektiver Status: die Farbe sagt die Wahrheit, die Marke die Box
|
## D39 — Effektiver Status: die Farbe sagt die Wahrheit, die Marke die Box
|
||||||
Mit den Abhängigkeiten (D37) gibt es zwei Aussagen je Knoten: was in der
|
Mit den Abhängigkeiten (D37) gibt es zwei Aussagen je Knoten: was in der
|
||||||
Statusbox steht (intrinsisch) und wie weit er wirklich ist (effektiv, §4).
|
Statusbox steht (intrinsisch) und wie weit er wirklich ist (effektiv, §4).
|
||||||
|
|||||||
+4
-1
@@ -1214,7 +1214,10 @@ 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
|
nackte Glyph war zu klein). Klick auf das Zeichen klappt um — der einfache
|
||||||
Klick auf den Knoten selbst
|
Klick auf den Knoten selbst
|
||||||
öffnet das Knoten-Fenster (§9, D92); Tastatur: **←** klappt zu, **→** klappt
|
öffnet das Knoten-Fenster (§9, D92); Tastatur: **←** klappt zu, **→** klappt
|
||||||
auf am fokussierten Knoten (WAI-ARIA-Baum-Idiom).
|
auf am fokussierten Knoten (WAI-ARIA-Baum-Idiom). Der umgeklappte Knoten
|
||||||
|
**behält dabei seine Position im Bild**: Der Neubau verschiebt den Inhalt,
|
||||||
|
der Scrollstand wird um die Differenz nachgezogen — soweit der Rand es
|
||||||
|
zulässt (D38-Nachtrag 5).
|
||||||
|
|
||||||
- **Anfangszustand aus dem Text (§1):** `>` klappt ein. `<` holt seinen
|
- **Anfangszustand aus dem Text (§1):** `>` klappt ein. `<` holt seinen
|
||||||
Teilbaum hervor, indem die Faltung die Pfad-Ebenen **hinunterwandert**:
|
Teilbaum hervor, indem die Faltung die Pfad-Ebenen **hinunterwandert**:
|
||||||
|
|||||||
+18
-4
@@ -1465,11 +1465,24 @@ function applyFoldPreset(mode){
|
|||||||
sonst risse die Tastaturbedienung ab (das alte Element ist weg) — aber ohne
|
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
|
das Knoten-Fenster zu rufen: Falten ist genau die Geste, der das Fenster im
|
||||||
Weg stand (D92), und die `:focus-visible`-Heuristik ist beim
|
Weg stand (D92), und die `:focus-visible`-Heuristik ist beim
|
||||||
programmatischen Fokus nicht verlässlich genug für diese Zusage. */
|
programmatischen Fokus nicht verlässlich genug für diese Zusage.
|
||||||
|
Und er behält seine POSITION IM BILD (D38-Nachtrag 5): Der Neubau verschiebt
|
||||||
|
den Inhalt (der Teilbaum ändert die Breite alles Umliegenden), der Scrollstand
|
||||||
|
von `.diagram` bliebe stehen — der geklickte Knoten spränge. Vorher wird seine
|
||||||
|
Sichtfenster-Position gemessen, danach der Scrollstand um die Differenz
|
||||||
|
nachgezogen. Beide Rects sind Viewport-Koordinaten des ungezoomten
|
||||||
|
Scroll-Containers (der `zoom` sitzt auf #out DARIN), die Differenz ist also
|
||||||
|
direkt scrollbar; am Rand klemmt der Browser — bestmöglich statt exakt. */
|
||||||
let suppressTipFocus = false;
|
let suppressTipFocus = false;
|
||||||
function refocusNode(line){
|
function refocusNode(line, before){
|
||||||
const again = out.querySelector('.node[data-line="' + line + '"]');
|
const again = out.querySelector('.node[data-line="' + line + '"]');
|
||||||
if(!again) return;
|
if(!again) return;
|
||||||
|
if(before){
|
||||||
|
const after = again.getBoundingClientRect();
|
||||||
|
const sc = document.querySelector('.diagram');
|
||||||
|
sc.scrollLeft += after.left - before.left;
|
||||||
|
sc.scrollTop += after.top - before.top;
|
||||||
|
}
|
||||||
suppressTipFocus = true; /* focusin läuft synchron im focus() */
|
suppressTipFocus = true; /* focusin läuft synchron im focus() */
|
||||||
again.focus({preventScroll: true});
|
again.focus({preventScroll: true});
|
||||||
suppressTipFocus = false;
|
suppressTipFocus = false;
|
||||||
@@ -1478,20 +1491,21 @@ function toggleFold(el){
|
|||||||
const line = +el.dataset.line;
|
const line = +el.dataset.line;
|
||||||
const st = foldByLine.get(line);
|
const st = foldByLine.get(line);
|
||||||
if(!st || !st.canFold) return;
|
if(!st || !st.canFold) return;
|
||||||
|
const before = el.getBoundingClientRect();
|
||||||
/* Bei aktiver Personen-Linse (D87) bleibt die Hand-Faltung in deren
|
/* Bei aktiver Personen-Linse (D87) bleibt die Hand-Faltung in deren
|
||||||
Sitzungs-Überlagerung — in den Text geschrieben würde sonst der
|
Sitzungs-Überlagerung — in den Text geschrieben würde sonst der
|
||||||
persönliche Filter, in geteilten Dokumenten für alle. */
|
persönliche Filter, in geteilten Dokumenten für alle. */
|
||||||
if(lens){
|
if(lens){
|
||||||
lensOverrides.set(st.key, !st.collapsed);
|
lensOverrides.set(st.key, !st.collapsed);
|
||||||
render();
|
render();
|
||||||
refocusNode(line);
|
refocusNode(line, before);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foldOverrides.set(st.key, !st.collapsed);
|
foldOverrides.set(st.key, !st.collapsed);
|
||||||
/* Das Schreiben löst per `input`-Ereignis schon ein render() aus. */
|
/* Das Schreiben löst per `input`-Ereignis schon ein render() aus. */
|
||||||
if(writeFoldToText(line, !st.collapsed)) foldOverrides.clear();
|
if(writeFoldToText(line, !st.collapsed)) foldOverrides.clear();
|
||||||
else render();
|
else render();
|
||||||
refocusNode(line);
|
refocusNode(line, before);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Klick auf das Falt-Zeichen ▾/▸ klappt um. preventDefault, weil das Zeichen
|
/* Klick auf das Falt-Zeichen ▾/▸ klappt um. preventDefault, weil das Zeichen
|
||||||
|
|||||||
Reference in New Issue
Block a user