Skip to content

Commit 38d46a0

Browse files
committed
Address review comments
1 parent 6370851 commit 38d46a0

File tree

1 file changed

+22
-16
lines changed

1 file changed

+22
-16
lines changed

src/pydata_sphinx_theme/assets/scripts/pydata-sphinx-theme.js

+22-16
Original file line numberDiff line numberDiff line change
@@ -991,9 +991,15 @@ async function addTOCScrollSpy() {
991991
const options = {
992992
root: null, // Target the viewport
993993
// 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).
9951001
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
9971003
};
9981004

9991005
const pageToc = document.querySelector("#pst-page-toc-nav");
@@ -1020,7 +1026,8 @@ async function addTOCScrollSpy() {
10201026
tocElement.setAttribute("aria-current", "true");
10211027

10221028
// 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.
10241031
const parents = new Set();
10251032
let el = tocElement.parentElement;
10261033
while (el && el !== pageToc) {
@@ -1032,7 +1039,9 @@ async function addTOCScrollSpy() {
10321039

10331040
// Iterate over all child elements of the TOC, deactivating everything
10341041
// 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.
10361045
pageToc.querySelectorAll(".toc-entry").forEach((el) => {
10371046
if (parents.has(el)) {
10381047
el.classList.add("active");
@@ -1059,13 +1068,12 @@ async function addTOCScrollSpy() {
10591068
// in the article to TOC elements, along with information about whether they are
10601069
// visible and the order in which they appear in the article.
10611070
const headingState = new Map(
1062-
Array.from(tocLinks).map((el, index) => {
1071+
Array.from(tocLinks).map((el) => {
10631072
return [
10641073
getHeading(el),
10651074
{
10661075
tocElement: el,
10671076
visible: false,
1068-
index: index,
10691077
},
10701078
];
10711079
}),
@@ -1083,25 +1091,23 @@ async function addTOCScrollSpy() {
10831091
headingState.get(entry.target).visible = entry.isIntersecting;
10841092
});
10851093

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-
10911094
// If there are any visible results, activate the one _above_ the first visible
10921095
// heading. This ensures that when a heading scrolls offscreen, the TOC entry
10931096
// for that entry remains highlighted.
10941097
//
10951098
// 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]);
10991105
}
11001106
}
11011107

11021108
const observer = new IntersectionObserver(callback, options);
1103-
tocLinks.forEach((tocElement) => {
1104-
observer.observe(getHeading(tocElement));
1109+
headingState.forEach((_, heading) => {
1110+
observer.observe(heading);
11051111
});
11061112
}
11071113

0 commit comments

Comments
 (0)