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;
|
||||
}
|
||||
function treePrefix({ depth, ancestorIsLast, isLast }) {
|
||||
if (depth === 0) return '';
|
||||
let s = '';
|
||||
// Ancestor at depth 0 (root) doesn't get a vertical-line column —
|
||||
// roots are separated visually as top-level rows already.
|
||||
// Builds the .tree-prefix DOM for a row at the given depth. Each
|
||||
// lane is its own positioned child so CSS can paint full-height
|
||||
// vertical bars that bridge the gap between sibling rows — text
|
||||
// box-drawing glyphs only paint one text-line tall, which left
|
||||
// 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++) {
|
||||
s += ancestorIsLast[d] ? ' ' : '│ ';
|
||||
const cls = ancestorIsLast[d] ? 'tree-lane lane-blank' : 'tree-lane lane-line';
|
||||
prefix.append(el('span', { class: cls }));
|
||||
}
|
||||
s += isLast ? '└─ ' : '├─ ';
|
||||
return s;
|
||||
const jointCls = 'tree-lane lane-joint ' + (isLast ? 'lane-joint-last' : 'lane-joint-branch');
|
||||
prefix.append(el('span', { class: jointCls }));
|
||||
return prefix;
|
||||
}
|
||||
|
||||
function renderContainers(s) {
|
||||
|
|
@ -731,10 +744,8 @@ window.marked = marked;
|
|||
// legacy flat layout (every container at depth 0) is bit-
|
||||
// identical to today's render — no glyph, no indent.
|
||||
if (node.depth > 0) li.dataset.depth = String(node.depth);
|
||||
const prefix = treePrefix(node);
|
||||
if (prefix) {
|
||||
li.prepend(el('span', { class: 'tree-prefix', 'aria-hidden': 'true' }, prefix));
|
||||
}
|
||||
const prefix = treePrefixDom(node);
|
||||
if (prefix) li.prepend(prefix);
|
||||
|
||||
// Full-height square agent icon, left of the card body. The
|
||||
// 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="5"] { margin-left: 9em; }
|
||||
.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 {
|
||||
/* 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;
|
||||
left: -1.6em;
|
||||
top: 0.6em;
|
||||
color: var(--purple-dim);
|
||||
font-family: inherit;
|
||||
white-space: pre;
|
||||
/* Extend into the `.containers { gap: 0.4em }` below so vertical
|
||||
bars connect to the next sibling's prefix without a visual gap. */
|
||||
top: 0;
|
||||
bottom: -0.4em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
pointer-events: 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
|
||||
the plain stacked block layout. The icon is a background-image div
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue