fix: vertikale Sammelleiste erreicht den Eltern-Stub wieder (D65)

Zwei Geometrie-Fehler bei Gruppen mit wenigen Kindern: :only-child schaltete
die Leiste ab, obwohl Stub (50 %) und Abzweig (23 px) seit dem 20-px-
Zusatzabstand nicht mehr zusammenfallen (6,6–15,8 px Lücke); und bei einem
großen Teilbaum im letzten Kind endete die Leiste über dem Stub (bis 98 px).
Fix: CSS-Verbinder 23px→50 % am Einzelkind, alignVRails() + --vrail-ext für
das letzte Kind. Export und Kompakt-Modus waren nie betroffen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-24 18:19:19 +02:00
co-authored by Claude Fable 5
parent 94aa16efbd
commit 07e4116bd9
5 changed files with 129 additions and 2 deletions
+29 -1
View File
@@ -206,6 +206,7 @@ function render(){
renderLineNos();
applyOptStairs(); /* muss vor dem Messen laufen — es verschiebt Knoten */
alignStems();
alignVRails();
drawCheapPath();
/* Querverbindungen (D41) zeichnet highlightCurrentNode() unten mit
es kennt die zweite Hälfte der Auswahl (Cursor-Zeile). */
@@ -332,6 +333,32 @@ function alignStems(){
});
}
/* Sammelleisten-Verlängerung im vertikalen Modus (D65). Der Eltern-Stub dockt
bei 50 % der Gruppenhöhe an, die Leiste endet aber am 23-px-Abzweig des
letzten Kindes trägt das einen großen Teilbaum, liegt die Gruppenmitte
TIEFER und der Stub hinge in der Luft (gemessen: 4,5 bis 98 px Lücke). CSS
kann die Gruppenmitte relativ zum letzten <li> nicht ausdrücken; wie bei
`alignStems()` misst deshalb JS und setzt `--vrail-ext` (unskalierte px,
durch effZoom() zurückgerechnet). Nur nicht-has-and-Letztkinder: Bei
has-and liegt der Abzweig bei 50 % der Zelle, und die Gruppenmitte liegt
beweisbar nie darunter. Nach oben kann die Mitte nie aus der Leiste fallen
(der erste Abzweig liegt höchstens 23 px unter dem Gruppenanfang). */
function alignVRails(){
out.querySelectorAll('ul.and>li').forEach(li => li.style.removeProperty('--vrail-ext'));
if(!out.classList.contains('vertical')) return;
const z = effZoom() || 1;
out.querySelectorAll('li.has-and>ul.and').forEach(ul => {
const kids = [...ul.children].filter(e => e.tagName === 'LI');
if(kids.length < 2) return; /* :only-child löst CSS allein (50 %) */
const last = kids[kids.length - 1];
if(last.classList.contains('has-and')) return;
const ur = ul.getBoundingClientRect(), lr = last.getBoundingClientRect();
if(!ur.height) return; /* Panel eingeklappt */
const ext = (ur.top + ur.height/2 - lr.top)/z;
if(ext > 23.5) last.style.setProperty('--vrail-ext', ext.toFixed(1) + 'px');
});
}
function drawCheapPath(){
out.querySelectorAll('svg.cheap-overlay').forEach(e => e.remove());
if(!cheapPathOn) return;
@@ -1752,6 +1779,7 @@ function applyLayout(mode){
if(!isMobile()) applySplit(); /* Desktop: Preset neu setzen. Mobil: freie --drow-Aufteilung behalten */
applyOptStairs(); /* Treppe gilt nur im Fächer — beim Moduswechsel bauen/auflösen */
alignStems(); /* Stiel gilt nur im Fächer — beim Moduswechsel neu setzen/löschen */
alignVRails(); /* Leisten-Verlängerung gilt nur vertikal — ebenso (D65) */
drawCheapPath(); /* Blatt-Positionen ändern sich mit dem Modus */
drawDepLinks(); /* Knoten-Positionen ebenso (D41) */
}
@@ -1805,7 +1833,7 @@ function setMobilePane(pane, save){
Live-Geometrie zeichnet, muss deshalb nach dem Sichtbarwerden neu laufen
im Diagramm dieselben vier Schritte wie beim Moduswechsel (applyLayout),
im Editor der Zeilennummern-Streifen, der am Spiegel misst (D33). */
if(pane === 'diagram'){ applyOptStairs(); alignStems(); drawCheapPath(); drawDepLinks(); }
if(pane === 'diagram'){ applyOptStairs(); alignStems(); alignVRails(); drawCheapPath(); drawDepLinks(); }
else renderLineNos();
if(save) saveUI();
}
+13 -1
View File
@@ -1317,7 +1317,19 @@
Linien zu zentrierten Zwischenknoten ins Leere. Die Enden der Sammelleiste
folgen demselben Abzweigpunkt des jeweiligen Rand-Kindes. */
.tree.vertical li.has-and>ul.and>li:first-child::after{top:23px;height:calc(100% - 23px)}
.tree.vertical li.has-and>ul.and>li:only-child::after{border:0}
/* Einziges Kind: KEIN border:0 mehr (D65). Der Eltern-Stub dockt bei 50 % der
Gruppenhöhe an, der Kind-Abzweig liegt fest bei 23 px — seit dem
20-px-Zusatzabstand unten (und erst recht bei mehrzeiligen Knoten, D64)
fallen die beiden NICHT mehr zusammen. Das Verbindungsstück ist hier rein
in CSS ausdrückbar, weil Gruppenhöhe == Kindhöhe (ul hat padding-top:0). */
.tree.vertical li.has-and>ul.and>li:only-child::after{top:23px;height:max(0px, calc(50% - 23px))}
/* Letztes Kind mit großem Teilbaum (D65): Die Leiste endet am 23-px-Abzweig,
der Eltern-Stub dockt aber bei 50 % der GRUPPE an — liegt der tiefer
(großer Teilbaum im letzten Kind), hinge er in der Luft. `alignVRails()`
in app.js misst das und verlängert die Leiste per --vrail-ext bis zum
Stub. has-and-Kinder brauchen das nie (Abzweig bei 50 % der Zelle, und
50 % der Gruppe liegt beweisbar darüber). */
.tree.vertical li.has-and>ul.and>li:last-child:not(.has-and):not(:only-child)::after{height:var(--vrail-ext, 23px)}
.tree.vertical li.has-and>ul.and>li.has-and::before{top:50%}
.tree.vertical li.has-and>ul.and>li.has-and:first-child::after{top:50%;height:50%}
.tree.vertical li.has-and>ul.and>li.has-and:last-child::after{top:0;height:50%}