@@ -991,9 +991,15 @@ 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. The factor of -1 is to move the location where the
996
+ // intersection triggers downward below this element; a factor of 2 was found to give
997
+ // good results in testing. If this is only -1 (the exact size of the top menu bar),
998
+ // the intersection doesn't trigger until the heading is scrolled off the top of the screen
999
+ // behind the menu bar, which also means that when clicking on a link in the TOC doesn't
1000
+ // trigger the intersection (and therefore doesn't highlight the correct heading).
995
1001
rootMargin : `${ - 2 * document . querySelector ( "header.bd-header" ) . getBoundingClientRect ( ) . bottom } px 0px 0px 0px` ,
996
- threshold : 0 , // Trigger as soon as 1 pixel is visible
1002
+ threshold : 0 , // Trigger as soon as it becomes visible or invisible
997
1003
} ;
998
1004
999
1005
const pageToc = document . querySelector ( "#pst-page-toc-nav" ) ;
@@ -1020,7 +1026,8 @@ async function addTOCScrollSpy() {
1020
1026
tocElement . setAttribute ( "aria-current" , "true" ) ;
1021
1027
1022
1028
// Travel up the DOM from the requested element, collecting the set of
1023
- // all parent elements that need to be activated
1029
+ // all parent elements that need to be activated. These are the collapsible
1030
+ // <li> elements that can hold nested child headings.
1024
1031
const parents = new Set ( ) ;
1025
1032
let el = tocElement . parentElement ;
1026
1033
while ( el && el !== pageToc ) {
@@ -1032,7 +1039,9 @@ async function addTOCScrollSpy() {
1032
1039
1033
1040
// Iterate over all child elements of the TOC, deactivating everything
1034
1041
// that isn't a parent of the active node and activating the parents
1035
- // of the active TOC entry
1042
+ // of the active TOC entry. This closes all collapsible <li> elements
1043
+ // of which the active element is not a direct descendent, and activates
1044
+ // those which are.
1036
1045
pageToc . querySelectorAll ( ".toc-entry" ) . forEach ( ( el ) => {
1037
1046
if ( parents . has ( el ) ) {
1038
1047
el . classList . add ( "active" ) ;
@@ -1059,13 +1068,12 @@ async function addTOCScrollSpy() {
1059
1068
// in the article to TOC elements, along with information about whether they are
1060
1069
// visible and the order in which they appear in the article.
1061
1070
const headingState = new Map (
1062
- Array . from ( tocLinks ) . map ( ( el , index ) => {
1071
+ Array . from ( tocLinks ) . map ( ( el ) => {
1063
1072
return [
1064
1073
getHeading ( el ) ,
1065
1074
{
1066
1075
tocElement : el ,
1067
1076
visible : false ,
1068
- index : index ,
1069
1077
} ,
1070
1078
] ;
1071
1079
} ) ,
@@ -1083,25 +1091,23 @@ async function addTOCScrollSpy() {
1083
1091
headingState . get ( entry . target ) . visible = entry . isIntersecting ;
1084
1092
} ) ;
1085
1093
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
1094
// If there are any visible results, activate the one _above_ the first visible
1092
1095
// heading. This ensures that when a heading scrolls offscreen, the TOC entry
1093
1096
// for that entry remains highlighted.
1094
1097
//
1095
1098
// 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 ] ) ;
1099
+ const visible = Array . from ( headingState . values ( ) ) . filter (
1100
+ ( { visible } ) => visible ,
1101
+ ) ;
1102
+ if ( visible . length > 0 ) {
1103
+ const indexAbove = Math . max ( sorted [ 0 ] . index - 1 , 0 ) ;
1104
+ activate ( tocLinks [ indexAbove ] ) ;
1099
1105
}
1100
1106
}
1101
1107
1102
1108
const observer = new IntersectionObserver ( callback , options ) ;
1103
- tocLinks . forEach ( ( tocElement ) => {
1104
- observer . observe ( getHeading ( tocElement ) ) ;
1109
+ headingState . forEach ( ( _ , heading ) => {
1110
+ observer . observe ( heading ) ;
1105
1111
} ) ;
1106
1112
}
1107
1113
0 commit comments