11<template >
2- <nav class =" page-toc" aria-label =" Table of contents" >
2+ <nav class =" page-toc" :class = " { 'page-toc--joined': joined } " aria-label =" Table of contents" >
33 <ul
44 class =" page-toc-list"
55 :class =" { 'page-toc-list--animated': markerAnimated }"
@@ -69,6 +69,15 @@ const props = defineProps({
6969 selected: {
7070 type: String ,
7171 default: null
72+ },
73+ /**
74+ * Whether the rail turns out to the left at each end to meet a border drawn down the column's left
75+ * edge, rather than stopping level with the first and last label. Only the caller knows whether
76+ * there is such a border to meet, and how far off it is: see `--page-toc-reach` in the stylesheet.
77+ */
78+ joined: {
79+ type: Boolean ,
80+ default: false
7281 }
7382})
7483
@@ -333,6 +342,23 @@ onBeforeUnmount(() => {
333342 -- page- toc- ink- soft: #{$grey- 6 };
334343 -- page- toc- ink- hover: #{$grey- 10 };
335344 -- page- toc- hover- surface: rgba (0 , 0 , 0 , 0.04 );
345+ /*
346+ The space between the rail and a top-level label. The list is pulled left by the same amount, so
347+ the labels start on this component's own left edge -- level with whatever else the caller lines up
348+ there -- and the rail hangs out into the caller's padding.
349+ */
350+ -- page- toc- gutter: 9px ;
351+ /* How far left of the rail the border a joined rail meets is: what is left of the caller's `px-4` */
352+ -- page- toc- reach: calc (1rem - var (-- page- toc- gutter));
353+ /* The radius of a joined rail's two turns, a little inside the reach so each keeps a short straight */
354+ -- page- toc- turn: 6px ;
355+ /* The active marker's thickness, measured left from the rail's right-hand edge */
356+ -- page- toc- marker- w: 4px ;
357+ /*
358+ What a joined rail encloses between itself and the border: half the article's own white, so the
359+ strip reads as the edge of the page reaching into the column rather than as a gap in it.
360+ */
361+ -- page- toc- reach- fill: rgba (255 , 255 , 255 , 0.5 );
336362
337363 line- height: 1.4 ;
338364
@@ -343,11 +369,13 @@ onBeforeUnmount(() => {
343369 -- page- toc- ink- soft: rgba (255 , 255 , 255 , 0.45 );
344370 -- page- toc- ink- hover: #fff;
345371 -- page- toc- hover- surface: rgba (255 , 255 , 255 , 0.06 );
372+ /* -> Half the article's dark ground, `$dark-6`, for the same reason: white would glare here */
373+ -- page- toc- reach- fill: #{rgba ($dark- 6 , 0.5 )};
346374 }
347375
348376 & - list {
349377 position: relative;
350- margin: 0 ;
378+ margin: 0 0 0 calc ( - 1 * var ( -- page - toc - gutter)) ;
351379 padding: 0 ;
352380 list- style: none;
353381
@@ -363,15 +391,49 @@ onBeforeUnmount(() => {
363391 }
364392 }
365393
394+ /*
395+ The joined rail: in from the column's left border above the list, a quarter turn down, the rail,
396+ and a quarter turn back out to the border below it -- the same line round the contents that the
397+ history timeline draws round its entries.
398+
399+ As there, all three stretches are ONE border of ONE box -- the top, right and bottom edges of an
400+ invisible rectangle whose left edge is the column's -- so that the straights and the turns cannot
401+ come out at different thicknesses under fractional display scaling.
402+
403+ The turns sit outside the list, above and below it, so the whole height of the list is the
404+ straight stretch and the marker never rides onto a curve. The caller's padding is what they sit
405+ in. And it is drawn in the border's own colour rather than the rail's, since a line that changed
406+ shade where it met the border would read as two lines touching.
407+ */
408+ & -- joined & - list:: before {
409+ /*
410+ How far above the list the line comes in is the caller's to say, as `--page-toc-lead`: it is
411+ wherever the line it continues is drawn, which nothing here can know. At least the turn, or the
412+ curve would run into the first label.
413+ */
414+ top: calc (- 1 * max (var (-- page- toc- lead, 0px ), var (-- page- toc- turn)));
415+ bottom: calc (- 1 * var (-- page- toc- turn));
416+ left: calc (- 1 * var (-- page- toc- reach));
417+ box- sizing: border- box;
418+ /* -> Its right-hand border lands where the plain rail is, on the list's own left edge */
419+ width: calc (var (-- page- toc- reach) + 1px );
420+ border: 1px solid var (-- page- chrome- rule, var (-- page- toc- rail));
421+ border- left: 0 ;
422+ border- radius: 0 var (-- page- toc- turn) var (-- page- toc- turn) 0 ;
423+ background- color: var (-- page- toc- reach- fill);
424+ }
425+
366426 & - item {
367427 position: relative;
368428 /* Depth is carried as a custom property by the template, so one rule indents every level */
369429 padding- left: calc (var (-- page- toc- depth) * var (-- page- toc- indent));
370430 }
371431
372432 /*
373- The active marker, drawn ON the rail rather than beside it: `left: 0` is the list's own border
374- box, which is where the rail is, so every depth marks the same line whatever its indentation.
433+ The active marker, drawn ON the rail rather than beside it: the rail is the list's first pixel,
434+ so every depth marks the same line whatever its indentation. Its right edge is the rail's right
435+ edge, and the extra width grows out to the left -- toward the border a joined rail meets, and away
436+ from the labels, which then sit the same distance from it as from the rail.
375437
376438 One element for the whole list rather than a pseudo on the active row, so that moving to the next
377439 heading is a slide down the rail instead of the marker being switched off one row and on another.
@@ -382,10 +444,12 @@ onBeforeUnmount(() => {
382444 content: ' ' ;
383445 position: absolute;
384446 top: 0 ;
385- left: 0 ;
386- width: 2px ;
447+ /* -> The rail is 1px wide from `left: 0` */
448+ left: calc (1px - var (-- page- toc- marker- w));
449+ width: var (-- page- toc- marker- w);
387450 height: var (-- page- toc- marker- h, 0 );
388- border- radius: 1px ;
451+ /* -> Square on the rail's side, so it sits flush along the line it marks */
452+ border- radius: calc (var (-- page- toc- marker- w) / 2 ) 0 0 calc (var (-- page- toc- marker- w) / 2 );
389453 background- color: var (-- color- primary);
390454 opacity: var (-- page- toc- marker- opacity, 0 );
391455 transform: translateY (var (-- page- toc- marker- y, 0 ));
@@ -401,8 +465,8 @@ onBeforeUnmount(() => {
401465
402466 & - link {
403467 display: block;
404- /* 9px of gutter, not a caret column: the rail is the only thing to the left of a label */
405- padding: 3px 8px 3px 9px ;
468+ /* A gutter, not a caret column: the rail is the only thing to the left of a label */
469+ padding: 3px 8px 3px var ( -- page - toc - gutter) ;
406470 border- radius: 4px ;
407471 color: inherit;
408472 font- size: inherit;
0 commit comments