dashboard: connect tree-prefix vertical bars across taller rows (#388)
The container-row tree-prefix used text box-drawing glyphs (├ └ │) positioned with `top: 0.6em` — a single text-line tall. Once rows grew past one line (5em square icon + multi-line body), the `│` columns of consecutive siblings no longer touched, leaving visible breaks in the tree. Replace the text-glyph string with structured DOM: one `.tree-lane` per depth column. Continuation lanes (`.lane-line`) paint a 1px border-left spanning the full row height + the `.containers` gap below, so adjacent siblings' bars visually merge into one unbroken vertical. The row's own joint lane is `├` (branch — bar continues below) or `└` (last — bar stops at icon midline), with a horizontal stub at 3.1em (row padding-top + icon half-height) reaching to the icon edge. Joint y / stub width are derived from the 5em icon + 0.6em row padding-top + 0.8em row padding-left so they meet the icon cleanly.
This commit is contained in:
parent
47403595f1
commit
7743c07380
2 changed files with 89 additions and 20 deletions
|
|
@ -649,16 +649,29 @@ window.marked = marked;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
function treePrefix({ depth, ancestorIsLast, isLast }) {
|
// Builds the .tree-prefix DOM for a row at the given depth. Each
|
||||||
if (depth === 0) return '';
|
// lane is its own positioned child so CSS can paint full-height
|
||||||
let s = '';
|
// vertical bars that bridge the gap between sibling rows — text
|
||||||
// Ancestor at depth 0 (root) doesn't get a vertical-line column —
|
// box-drawing glyphs only paint one text-line tall, which left
|
||||||
// roots are separated visually as top-level rows already.
|
// visible breaks between rows once we grew taller-than-one-line
|
||||||
|
// container cards (#388). Ancestor lanes are either continuation
|
||||||
|
// (vertical bar top→bottom+gap) or blank; the joint at this row's
|
||||||
|
// own depth is ├ (branch — vertical continues below) or └ (last —
|
||||||
|
// vertical stops at the row's icon midline). CSS at
|
||||||
|
// `.container-row .tree-prefix` paints the bars + horizontal stub.
|
||||||
|
function treePrefixDom({ depth, ancestorIsLast, isLast }) {
|
||||||
|
if (depth === 0) return null;
|
||||||
|
const prefix = el('span', { class: 'tree-prefix', 'aria-hidden': 'true' });
|
||||||
|
// Ancestor columns (depth 1..depth-1). Skip depth 0 (root has no
|
||||||
|
// continuation column — top-level rows are separated visually as
|
||||||
|
// top-level rows already).
|
||||||
for (let d = 1; d < depth; d++) {
|
for (let d = 1; d < depth; d++) {
|
||||||
s += ancestorIsLast[d] ? ' ' : '│ ';
|
const cls = ancestorIsLast[d] ? 'tree-lane lane-blank' : 'tree-lane lane-line';
|
||||||
|
prefix.append(el('span', { class: cls }));
|
||||||
}
|
}
|
||||||
s += isLast ? '└─ ' : '├─ ';
|
const jointCls = 'tree-lane lane-joint ' + (isLast ? 'lane-joint-last' : 'lane-joint-branch');
|
||||||
return s;
|
prefix.append(el('span', { class: jointCls }));
|
||||||
|
return prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderContainers(s) {
|
function renderContainers(s) {
|
||||||
|
|
@ -731,10 +744,8 @@ window.marked = marked;
|
||||||
// legacy flat layout (every container at depth 0) is bit-
|
// legacy flat layout (every container at depth 0) is bit-
|
||||||
// identical to today's render — no glyph, no indent.
|
// identical to today's render — no glyph, no indent.
|
||||||
if (node.depth > 0) li.dataset.depth = String(node.depth);
|
if (node.depth > 0) li.dataset.depth = String(node.depth);
|
||||||
const prefix = treePrefix(node);
|
const prefix = treePrefixDom(node);
|
||||||
if (prefix) {
|
if (prefix) li.prepend(prefix);
|
||||||
li.prepend(el('span', { class: 'tree-prefix', 'aria-hidden': 'true' }, prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-height square agent icon, left of the card body. The
|
// Full-height square agent icon, left of the card body. The
|
||||||
// icon is an <img> absolutely positioned inside a wrapper div:
|
// icon is an <img> absolutely positioned inside a wrapper div:
|
||||||
|
|
|
||||||
|
|
@ -204,18 +204,76 @@ a:hover {
|
||||||
.container-row[data-depth="4"] { margin-left: 7.2em; }
|
.container-row[data-depth="4"] { margin-left: 7.2em; }
|
||||||
.container-row[data-depth="5"] { margin-left: 9em; }
|
.container-row[data-depth="5"] { margin-left: 9em; }
|
||||||
.container-row[data-depth="6"] { margin-left: 10.8em; }
|
.container-row[data-depth="6"] { margin-left: 10.8em; }
|
||||||
|
/* Tree prefix sits in the left margin and paints the connecting
|
||||||
|
├ / └ / │ lanes as CSS rules rather than text glyphs (#388). Each
|
||||||
|
ancestor depth gets its own `.tree-lane` so we can paint a
|
||||||
|
full-row-height vertical bar that extends through the
|
||||||
|
.containers row gap into the next sibling — text box-drawing
|
||||||
|
glyphs only fill one text line, which left visible breaks
|
||||||
|
between rows once cards grew taller than one line of text (5em
|
||||||
|
square icons + multi-line body). The horizontal stub at the row's
|
||||||
|
own joint lands at the icon midline so the L/T meets the icon
|
||||||
|
edge cleanly. */
|
||||||
.container-row .tree-prefix {
|
.container-row .tree-prefix {
|
||||||
/* Tree-line glyphs sit in the left margin so the rest of the
|
|
||||||
container card layout (icon + body flex split) stays unchanged.
|
|
||||||
Mono font for column alignment with the ├─ / └─ / │ bricks. */
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -1.6em;
|
/* Extend into the `.containers { gap: 0.4em }` below so vertical
|
||||||
top: 0.6em;
|
bars connect to the next sibling's prefix without a visual gap. */
|
||||||
color: var(--purple-dim);
|
top: 0;
|
||||||
font-family: inherit;
|
bottom: -0.4em;
|
||||||
white-space: pre;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
color: var(--purple-dim);
|
||||||
|
}
|
||||||
|
/* Each depth step is one 1.8em lane wide — same step as the row's
|
||||||
|
own margin-left ladder above, so the rightmost lane (the joint)
|
||||||
|
sits flush against the row content (the icon). The prefix's left
|
||||||
|
edge is depth*1.8em LEFT of the row's left edge, so its right
|
||||||
|
edge meets the icon, and the leftmost lane lines up with the
|
||||||
|
top-level rows' icons at x=0. */
|
||||||
|
.container-row[data-depth="1"] .tree-prefix { left: -1.8em; }
|
||||||
|
.container-row[data-depth="2"] .tree-prefix { left: -3.6em; }
|
||||||
|
.container-row[data-depth="3"] .tree-prefix { left: -5.4em; }
|
||||||
|
.container-row[data-depth="4"] .tree-prefix { left: -7.2em; }
|
||||||
|
.container-row[data-depth="5"] .tree-prefix { left: -9em; }
|
||||||
|
.container-row[data-depth="6"] .tree-prefix { left: -10.8em; }
|
||||||
|
.tree-prefix .tree-lane {
|
||||||
|
flex: 0 0 1.8em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* Continuation bar — full row + gap below (so two adjacent
|
||||||
|
ancestor-line lanes from sibling rows visually merge into one
|
||||||
|
unbroken vertical line). Drawn at lane center (0.6em from left)
|
||||||
|
to keep it visually centered in the 1.8em column. */
|
||||||
|
.tree-prefix .lane-line::before,
|
||||||
|
.tree-prefix .lane-joint::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0.6em;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-left: 1px solid currentColor;
|
||||||
|
}
|
||||||
|
/* Last-child joint (└): vertical bar stops at the icon midline (no
|
||||||
|
continuation below this row). 3.1em ≈ row padding-top 0.6em +
|
||||||
|
icon half-height 2.5em, matching the horizontal stub's y below. */
|
||||||
|
.tree-prefix .lane-joint-last::before {
|
||||||
|
bottom: auto;
|
||||||
|
height: 3.1em;
|
||||||
|
}
|
||||||
|
/* Horizontal stub from the joint's vertical bar across the lane
|
||||||
|
and through the row's padding-left to the icon left edge. Lands
|
||||||
|
at the icon midline so the ├/└ meets the icon cleanly.
|
||||||
|
Width = lane-right (1.2em from lane center) + row padding-left
|
||||||
|
(0.8em) = 2em. */
|
||||||
|
.tree-prefix .lane-joint::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0.6em;
|
||||||
|
top: 3.1em;
|
||||||
|
width: 2em;
|
||||||
|
border-top: 1px solid currentColor;
|
||||||
}
|
}
|
||||||
/* Live cards get the icon-left / body-right split; tombstone rows keep
|
/* Live cards get the icon-left / body-right split; tombstone rows keep
|
||||||
the plain stacked block layout. The icon is a background-image div
|
the plain stacked block layout. The icon is a background-image div
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue