Skip to content

Commit 151623d

Browse files
authored
Highlight correct TOC entry on page load (#2185)
### Compare Preview URL - Kitchen Sink - Admonitions - with #topic in the hash portion of the URL: https://pydata-sphinx-theme--2185.org.readthedocs.build/en/2185/examples/kitchen-sink/admonitions.html#topic The entry for `topic` in the right sidebar table of contents is highlighted, as shown in the following screenshot: ![](https://github.com/user-attachments/assets/1d7091c9-1ccc-4f76-ada1-5ae225616cb0) This pull request fixes the following bug, which is currently in the dev version of the theme / latest version of the docs. Production URL - same page - #topic in hash: https://pydata-sphinx-theme.readthedocs.io/en/latest/examples/kitchen-sink/admonitions.html#topic The TOC entry for `admonition` (just below `topic`) is incorrectly highlighted, as shown in the following screenshot: > [!CAUTION] > ![](https://github.com/user-attachments/assets/6318e7bc-fb56-431f-b592-e92770f362ad)
1 parent 0ef98d8 commit 151623d

File tree

1 file changed

+70
-11
lines changed

1 file changed

+70
-11
lines changed

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

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,20 +1013,79 @@ function setupArticleTocSyncing() {
10131013
return;
10141014
}
10151015

1016-
// When the website visitor clicks a link in the TOC, we want that link to be
1017-
// highlighted/activated, NOT whichever TOC link the intersection observer
1018-
// callback would otherwise highlight, so we turn off the observer and turn it
1019-
// back on later.
1016+
// Create a boolean variable that allows us to turn off the intersection
1017+
// observer (and then later back on). When the website visitor clicks an
1018+
// in-page link, we want that entry in the TOC to be highlighted/activated,
1019+
// NOT whichever TOC link the intersection observer callback would otherwise
1020+
// highlight.
10201021
let disableObserver = false;
1021-
pageToc.addEventListener("click", (event) => {
1022+
1023+
function temporarilyDisableObserver(time) {
10221024
disableObserver = true;
1023-
const clickedTocLink = tocLinks.find((el) => el.contains(event.target));
1024-
activate(clickedTocLink);
10251025
setTimeout(() => {
1026-
// Give the page ample time to finish scrolling, then re-enable the
1027-
// intersection observer.
10281026
disableObserver = false;
1029-
}, 1000);
1027+
}, time);
1028+
}
1029+
1030+
/**
1031+
* If the provided URL hash fragment (beginning with "#") matches an entry in
1032+
* the page table of contents, highlight that entry and temporarily disable
1033+
* the intersection observer while the page scrolls to the corresponding
1034+
* heading.
1035+
*/
1036+
function syncTocHash(hash) {
1037+
if (hash.length > 1) {
1038+
const matchingTocLink = tocLinks.find((tocLink) => tocLink.hash === hash);
1039+
if (matchingTocLink) {
1040+
// It's important to disable the intersection observer before
1041+
// highlighting the TOC link and its corresponding article heading. This
1042+
// is because the browser takes a little time to scroll to the article
1043+
// heading, and while scrolling, it could trigger intersection events
1044+
// that cause some other link in the table of contents to be
1045+
// highlighted.
1046+
temporarilyDisableObserver(1000);
1047+
activate(matchingTocLink);
1048+
}
1049+
}
1050+
}
1051+
1052+
// On page load...
1053+
//
1054+
// When the page loads, sync the page's table of contents.
1055+
syncTocHash(window.location.hash);
1056+
1057+
// On navigation to another part of the page...
1058+
//
1059+
// When the user navigates to another part of the page, sync the page's table
1060+
// of contents.
1061+
window.addEventListener("hashchange", () => {
1062+
// By the time this event is fired, window.location.hash has already been
1063+
// updated with the new hash
1064+
syncTocHash(window.location.hash);
1065+
});
1066+
1067+
// On return to the same part of the page...
1068+
//
1069+
// The hashchange event will handle most cases where we need to sync the table
1070+
// of contents with a hash link click. But there is one edge case it doesn't
1071+
// handle, which is when the user clicks an internal page link whose hash is
1072+
// already in the browser address bar. For example, the user loads the page at
1073+
// #first-heading, scrolls to the bottom of the page, then clicks in the
1074+
// sidebar table of contents to go back up to the first heading in the
1075+
// article. In this case the "hashchange" event will not fire. Nonetheless, we
1076+
// want to guarantee that the TOC entry for the first heading gets
1077+
// highlighted. Note we cannot rely exclusively on the "click" event for all
1078+
// internal page navigations because it will not fire in the edge case where
1079+
// the user modifies the hash directly in the browser address bar.
1080+
window.addEventListener("click", (event) => {
1081+
const link = event.target.closest("a");
1082+
if (
1083+
link &&
1084+
link.hash === window.location.hash &&
1085+
link.origin === window.location.origin
1086+
) {
1087+
syncTocHash(link.hash);
1088+
}
10301089
});
10311090

10321091
/**
@@ -1127,7 +1186,7 @@ function setupArticleTocSyncing() {
11271186
}
11281187

11291188
observer = new IntersectionObserver(callback, options);
1130-
headingsToTocLinks.keys().forEach((heading) => {
1189+
Array.from(headingsToTocLinks.keys()).forEach((heading) => {
11311190
observer.observe(heading);
11321191
});
11331192
}

0 commit comments

Comments
 (0)