docs: Fix bug on table of contents (#43840)

Danilo Leal created

We were previously always highlighting the last header in the table of
contents as active even if you were at the top of the page. This is now
fixed, where upon loading the page, no header is highlighted, and as you
scroll, we highlight the top-most heading one by one.

Release Notes:

- N/A

Change summary

docs/theme/page-toc.js | 58 ++++++++++++++++++++++++++-----------------
1 file changed, 35 insertions(+), 23 deletions(-)

Detailed changes

docs/theme/page-toc.js 🔗

@@ -3,21 +3,18 @@ let scrollTimeout;
 const listenActive = () => {
   const elems = document.querySelector(".pagetoc").children;
   [...elems].forEach((el) => {
-    el.addEventListener("click", (event) => {
+    el.addEventListener("click", (_) => {
       clearTimeout(scrollTimeout);
       [...elems].forEach((el) => el.classList.remove("active"));
       el.classList.add("active");
-      // Prevent scroll updates for a short period
+
       scrollTimeout = setTimeout(() => {
         scrollTimeout = null;
-      }, 100); // Adjust timing as needed
+      }, 100);
     });
   });
 };
 
-const getPagetoc = () =>
-  document.querySelector(".pagetoc") || autoCreatePagetoc();
-
 const autoCreatePagetoc = () => {
   const main = document.querySelector("#content > main");
   const content = Object.assign(document.createElement("div"), {
@@ -27,30 +24,41 @@ const autoCreatePagetoc = () => {
   main.prepend(content);
   main.insertAdjacentHTML(
     "afterbegin",
-    '<div class="sidetoc"><nav class="pagetoc"></nav></div>',
+    '<div class="toc-container"><nav class="pagetoc"></nav></div>',
   );
   return document.querySelector(".pagetoc");
 };
+
+const getPagetoc = () =>
+  document.querySelector(".pagetoc") || autoCreatePagetoc();
+
 const updateFunction = () => {
-  if (scrollTimeout) return; // Skip updates if within the cooldown period from a click
+  if (scrollTimeout) return;
+
   const headers = [...document.getElementsByClassName("header")];
-  const scrolledY = window.scrollY;
-  let lastHeader = null;
-
-  // Find the last header that is above the current scroll position
-  for (let i = headers.length - 1; i >= 0; i--) {
-    if (scrolledY >= headers[i].offsetTop) {
-      lastHeader = headers[i];
-      break;
+  if (headers.length === 0) return;
+
+  const threshold = 100;
+  let activeHeader = null;
+
+  for (const header of headers) {
+    const rect = header.getBoundingClientRect();
+
+    if (rect.top <= threshold) {
+      activeHeader = header;
     }
   }
 
+  if (!activeHeader && headers.length > 0) {
+    activeHeader = headers[0];
+  }
+
   const pagetocLinks = [...document.querySelector(".pagetoc").children];
   pagetocLinks.forEach((link) => link.classList.remove("active"));
 
-  if (lastHeader) {
+  if (activeHeader) {
     const activeLink = pagetocLinks.find(
-      (link) => lastHeader.href === link.href,
+      (link) => activeHeader.href === link.href,
     );
     if (activeLink) activeLink.classList.add("active");
   }
@@ -63,13 +71,9 @@ document.addEventListener("DOMContentLoaded", () => {
   const nonH1Headers = headers.filter(
     (header) => !header.parentElement.tagName.toLowerCase().startsWith("h1"),
   );
-  const sidetoc = document.querySelector(".sidetoc");
   const tocContainer = document.querySelector(".toc-container");
 
   if (nonH1Headers.length === 0) {
-    if (sidetoc) {
-      sidetoc.style.display = "none";
-    }
     if (tocContainer) {
       tocContainer.classList.add("no-toc");
     }
@@ -84,6 +88,7 @@ document.addEventListener("DOMContentLoaded", () => {
     className: "toc-title",
     textContent: "On This Page",
   });
+
   pagetoc.appendChild(tocTitle);
 
   headers.forEach((header) => {
@@ -94,7 +99,14 @@ document.addEventListener("DOMContentLoaded", () => {
     });
     pagetoc.appendChild(link);
   });
+
   updateFunction();
   listenActive();
-  window.addEventListener("scroll", updateFunction);
+
+  const pageElement = document.querySelector(".page");
+  if (pageElement) {
+    pageElement.addEventListener("scroll", updateFunction);
+  } else {
+    window.addEventListener("scroll", updateFunction);
+  }
 });