@@ -991,9 +991,10 @@ async function addTOCScrollSpy() {
991
991
const options = {
992
992
root : null , // Target the viewport
993
993
// Offset the rootMargin slightly so that intersections trigger _before_ headings begin to
994
- // go offscreen
994
+ // go offscreen. Need to account for the height of the top menu bar which is sticky, and
995
+ // obscures the top part of the viewport.
995
996
rootMargin : `${ - 2 * document . querySelector ( "header.bd-header" ) . getBoundingClientRect ( ) . bottom } px 0px 0px 0px` ,
996
- threshold : 0 , // Trigger as soon as 1 pixel is visible
997
+ threshold : 0 , // Trigger as soon as it becomes visible or invisible
997
998
} ;
998
999
999
1000
const pageToc = document . querySelector ( "#pst-page-toc-nav" ) ;
@@ -1020,7 +1021,8 @@ async function addTOCScrollSpy() {
1020
1021
tocElement . setAttribute ( "aria-current" , "true" ) ;
1021
1022
1022
1023
// Travel up the DOM from the requested element, collecting the set of
1023
- // all parent elements that need to be activated
1024
+ // all parent elements that need to be activated. These are the collapsible
1025
+ // <li> elements that can hold nested child headings.
1024
1026
const parents = new Set ( ) ;
1025
1027
let el = tocElement . parentElement ;
1026
1028
while ( el && el !== pageToc ) {
@@ -1032,7 +1034,9 @@ async function addTOCScrollSpy() {
1032
1034
1033
1035
// Iterate over all child elements of the TOC, deactivating everything
1034
1036
// that isn't a parent of the active node and activating the parents
1035
- // of the active TOC entry
1037
+ // of the active TOC entry. This closes all collapsible <li> elements
1038
+ // of which the active element is not a direct descendent, and activates
1039
+ // those which are.
1036
1040
pageToc . querySelectorAll ( ".toc-entry" ) . forEach ( ( el ) => {
1037
1041
if ( parents . has ( el ) ) {
1038
1042
el . classList . add ( "active" ) ;
@@ -1059,13 +1063,12 @@ async function addTOCScrollSpy() {
1059
1063
// in the article to TOC elements, along with information about whether they are
1060
1064
// visible and the order in which they appear in the article.
1061
1065
const headingState = new Map (
1062
- Array . from ( tocLinks ) . map ( ( el , index ) => {
1066
+ Array . from ( tocLinks ) . map ( ( el ) => {
1063
1067
return [
1064
1068
getHeading ( el ) ,
1065
1069
{
1066
1070
tocElement : el ,
1067
1071
visible : false ,
1068
- index : index ,
1069
1072
} ,
1070
1073
] ;
1071
1074
} ) ,
@@ -1083,25 +1086,23 @@ async function addTOCScrollSpy() {
1083
1086
headingState . get ( entry . target ) . visible = entry . isIntersecting ;
1084
1087
} ) ;
1085
1088
1086
- // Sort the active headings by the order in which they appear in the TOC.
1087
- const sorted = Array . from ( headingState . values ( ) )
1088
- . filter ( ( { visible } ) => visible )
1089
- . sort ( ( a , b ) => a . index > b . index ) ;
1090
-
1091
1089
// If there are any visible results, activate the one _above_ the first visible
1092
1090
// heading. This ensures that when a heading scrolls offscreen, the TOC entry
1093
1091
// for that entry remains highlighted.
1094
1092
//
1095
1093
// If the first element is visible, just highlight the first entry in the TOC.
1096
- if ( sorted . length > 0 ) {
1097
- const idx = sorted [ 0 ] . index ;
1098
- activate ( tocLinks [ idx > 0 ? idx - 1 : 0 ] ) ;
1094
+ const visible = Array . from ( headingState . values ( ) ) . filter (
1095
+ ( { visible } ) => visible ,
1096
+ ) ;
1097
+ if ( visible . length > 0 ) {
1098
+ const indexAbove = Math . max ( sorted [ 0 ] . index - 1 , 0 ) ;
1099
+ activate ( tocLinks [ indexAbove ] ) ;
1099
1100
}
1100
1101
}
1101
1102
1102
1103
const observer = new IntersectionObserver ( callback , options ) ;
1103
- tocLinks . forEach ( ( tocElement ) => {
1104
- observer . observe ( getHeading ( tocElement ) ) ;
1104
+ headingState . forEach ( ( _ , heading ) => {
1105
+ observer . observe ( heading ) ;
1105
1106
} ) ;
1106
1107
}
1107
1108
0 commit comments