PageSourceSearch

https://eco-exp2.netlify.app/exp2_files/libs/quarto-html/quarto.js

js eco-exp2.netlify.app collected 2026-10-03 10:40:43 UTC 27,867 bytes, 884 lines download raw bytes

1const sectionChanged = new CustomEvent("quarto-sectionChanged", {
2  detail: {},
3  bubbles: true,
4  cancelable: false,
5  composed: false,
6});
7
8window.document.addEventListener("DOMContentLoaded", function (_event) {
9  const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
10  const sidebarEl = window.document.getElementById("quarto-sidebar");
11  const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
12  const marginSidebarEl = window.document.getElementById(
13    "quarto-margin-sidebar"
14  );
15  // function to determine whether the element has a previous sibling that is active
16  const prevSiblingIsActiveLink = (el) => {
17    const sibling = el.previousElementSibling;
18    if (sibling && sibling.tagName === "A") {
19      return sibling.classList.contains("active");
20    } else {
21      return false;
22    }
23  };
24
25  // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
26  function fireSlideEnter(e) {
27    const event = window.document.createEvent("Event");
28    event.initEvent("slideenter", true, true);
29    window.document.dispatchEvent(event);
30  }
31  const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
32  tabs.forEach((tab) => {
33    tab.addEventListener("shown.bs.tab", fireSlideEnter);
34  });
35
36  // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
37  document.addEventListener("tabby", fireSlideEnter, false);
38
39  // Track scrolling and mark TOC links as active
40  // get table of contents and sidebar (bail if we don't have at least one)
41  const tocLinks = tocEl
42    ? [...tocEl.querySelectorAll("a[data-scroll-target]")]
43    : [];
44  const makeActive = (link) => tocLinks[link].classList.add("active");
45  const removeActive = (link) => tocLinks[link].classList.remove("active");
46  const removeAllActive = () =>
47    [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
48
49  // activate the anchor for a section associated with this TOC entry
50  tocLinks.forEach((link) => {
51    link.addEventListener("click", () => {
52      if (link.href.indexOf("#") !== -1) {
53        const anchor = link.href.split("#")[1];
54        const heading = window.document.querySelector(
55          `[data-anchor-id=${anchor}]`
56        );
57        if (heading) {
58          // Add the class
59          heading.classList.add("reveal-anchorjs-link");
60
61          // function to show the anchor
62          const handleMouseout = () => {
63            heading.classList.remove("reveal-anchorjs-link");
64            heading.removeEventListener("mouseout", handleMouseout);
65          };
66
67          // add a function to clear the anchor when the user mouses out of it
68          heading.addEventListener("mouseout", handleMouseout);
69        }
70      }
71    });
72  });
73
74  const sections = tocLinks.map((link) => {
75    const target = link.getAttribute("data-scroll-target");
76    if (target.startsWith("#")) {
77      return window.document.getElementById(decodeURI(`${target.slice(1)}`));
78    } else {
79      return window.document.querySelector(decodeURI(`${target}`));
80    }
81  });
82
83  const sectionMargin = 200;
84  let currentActive = 0;
85  // track whether we've initialized state the first time
86  let init = false;
87
88  const updateActiveLink = () => {
89    // The index from bottom to top (e.g. reversed list)
90    let sectionIndex = -1;
91    if (
92      window.innerHeight + window.pageYOffset >=
93      window.document.body.offsetHeight
94    ) {
95      sectionIndex = 0;
96    } else {
97      sectionIndex = [...sections].reverse().findIndex((section) => {
98        if (section) {
99          return window.pageYOffset >= section.offsetTop - sectionMargin;
100        } else {
101          return false;
102        }
103      });
104    }
105    if (sectionIndex > -1) {
106      const current = sections.length - sectionIndex - 1;
107      if (current !== currentActive) {
108        removeAllActive();
109        currentActive = current;
110        makeActive(current);
111        if (init) {
112          window.dispatchEvent(sectionChanged);
113        }
114        init = true;
115      }
116    }
117  };
118
119  const inHiddenRegion = (top, bottom, hiddenRegions) => {
120    for (const region of hiddenRegions) {
121      if (top <= region.bottom && bottom >= region.top) {
122        return true;
123      }
124    }
125    return false;
126  };
127
128  const categorySelector = "header.quarto-title-block .quarto-category";
129  const activateCategories = (href) => {
130    // Find any categories
131    // Surround them with a link pointing back to:
132    // #category=Authoring
133    try {
134      const categoryEls = window.document.querySelectorAll(categorySelector);
135      for (const categoryEl of categoryEls) {
136        const categoryText = categoryEl.textContent;
137        if (categoryText) {
138          const link = `${href}#category=${encodeURIComponent(categoryText)}`;
139          const linkEl = window.document.createElement("a");
140          linkEl.setAttribute("href", link);
141          for (const child of categoryEl.childNodes) {
142            linkEl.append(child);
143          }
144          categoryEl.appendChild(linkEl);
145        }
146      }
147    } catch {
148      // Ignore errors
149    }
150  };
151  function hasTitleCategories() {
152    return window.document.querySelector(categorySelector) !== null;
153  }
154
155  function offsetRelativeUrl(url) {
156    const offset = getMeta("quarto:offset");
157    return offset ? offset + url : url;
158  }
159
160  function offsetAbsoluteUrl(url) {
161    const offset = getMeta("quarto:offset");
162    const baseUrl = new URL(offset, window.location);
163
164    const projRelativeUrl = url.replace(baseUrl, "");
165    if (projRelativeUrl.startsWith("/")) {
166      return projRelativeUrl;
167    } else {
168      return "/" + projRelativeUrl;
169    }
170  }
171
172  // read a meta tag value
173  function getMeta(metaName) {
174    const metas = window.document.getElementsByTagName("meta");
175    for (let i = 0; i < metas.length; i++) {
176      if (metas[i].getAttribute("name") === metaName) {
177        return metas[i].getAttribute("content");
178      }
179    }
180    return "";
181  }
182
183  async function findAndActivateCategories() {
184    const currentPagePath = offsetAbsoluteUrl(window.location.href);
185    const response = await fetch(offsetRelativeUrl("listings.json"));
186    if (response.status == 200) {
187      return response.json().then(function (listingPaths) {
188        const listingHrefs = [];
189        for (const listingPath of listingPaths) {
190          const pathWithoutLeadingSlash = listingPath.listing.substring(1);
191          for (const item of listingPath.items) {
192            if (
193              item === currentPagePath ||
194              item === currentPagePath + "index.html"
195            ) {
196              // Resolve this path against the offset to be sure
197              // we already are using the correct path to the listing
198              // (this adjusts the listing urls to be rooted against
199              // whatever root the page is actually running against)
200              const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
201              const baseUrl = window.location;
202              const resolvedPath = new URL(relative, baseUrl);
203              listingHrefs.push(resolvedPath.pathname);
204              break;
205            }
206          }
207        }
208
209        // Look up the tree for a nearby linting and use that if we find one
210        const nearestListing = findNearestParentListing(
211          offsetAbsoluteUrl(window.location.pathname),
212          listingHrefs
213        );
214        if (nearestListing) {
215          activateCategories(nearestListing);
216        } else {
217          // See if the referrer is a listing page for this item
218          const referredRelativePath = offsetAbsoluteUrl(document.referrer);
219          const referrerListing = listingHrefs.find((listingHref) => {
220            const isListingReferrer =
221              listingHref === referredRelativePath ||
222              listingHref === referredRelativePath + "index.html";
223            return isListingReferrer;
224          });
225
226          if (referrerListing) {
227            // Try to use the referrer if possible
228            activateCategories(referrerListing);
229          } else if (listingHrefs.length > 0) {
230            // Otherwise, just fall back to the first listing
231            activateCategories(listingHrefs[0]);
232          }
233        }
234      });
235    }
236  }
237  if (hasTitleCategories()) {
238    findAndActivateCategories();
239  }
240
241  const findNearestParentListing = (href, listingHrefs) => {
242    if (!href || !listingHrefs) {
243      return undefined;
244    }
245    // Look up the tree for a nearby linting and use that if we find one
246    const relativeParts = href.substring(1).split("/");
247    while (relativeParts.length > 0) {
248      const path = relativeParts.join("/");
249      for (const listingHref of listingHrefs) {
250        if (listingHref.startsWith(path)) {
251          return listingHref;
252        }
253      }
254      relativeParts.pop();
255    }
256
257    return undefined;
258  };
259
260  const manageSidebarVisiblity = (el, placeholderDescriptor) => {
261    let isVisible = true;
262    let elRect;
263
264    return (hiddenRegions) => {
265      if (el === null) {
266        return;
267      }
268
269      // Find the last element of the TOC
270      const lastChildEl = el.lastElementChild;
271
272      if (lastChildEl) {
273        // Converts the sidebar to a menu
274        const convertToMenu = () => {
275          for (const child of el.children) {
276            child.style.opacity = 0;
277            child.style.overflow = "hidden";
278          }
279
280          nexttick(() => {
281            const toggleContainer = window.document.createElement("div");
282            toggleContainer.style.width = "100%";
283            toggleContainer.classList.add("zindex-over-content");
284            toggleContainer.classList.add("quarto-sidebar-toggle");
285            toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
286            toggleContainer.id = placeholderDescriptor.id;
287            toggleContainer.style.position = "fixed";
288
289            const toggleIcon = window.document.createElement("i");
290            toggleIcon.classList.add("quarto-sidebar-toggle-icon");
291            toggleIcon.classList.add("bi");
292            toggleIcon.classList.add("bi-caret-down-fill");
293
294            const toggleTitle = window.document.createElement("div");
295            const titleEl = window.document.body.querySelector(
296              placeholderDescriptor.titleSelector
297            );
298            if (titleEl) {
299              toggleTitle.append(
300                titleEl.textContent || titleEl.innerText,
301                toggleIcon
302              );
303            }
304            toggleTitle.classList.add("zindex-over-content");
305            toggleTitle.classList.add("quarto-sidebar-toggle-title");
306            toggleContainer.append(toggleTitle);
307
308            const toggleContents = window.document.createElement("div");
309            toggleContents.classList = el.classList;
310            toggleContents.classList.add("zindex-over-content");
311            toggleContents.classList.add("quarto-sidebar-toggle-contents");
312            for (const child of el.children) {
313              if (child.id === "toc-title") {
314                continue;
315              }
316
317              const clone = child.cloneNode(true);
318              clone.style.opacity = 1;
319              clone.style.display = null;
320              toggleContents.append(clone);
321            }
322            toggleContents.style.height = "0px";
323            const positionToggle = () => {
324              // position the element (top left of parent, same width as parent)
325              if (!elRect) {
326                elRect = el.getBoundingClientRect();
327              }
328              toggleContainer.style.left = `${elRect.left}px`;
329              toggleContainer.style.top = `${elRect.top}px`;
330              toggleContainer.style.width = `${elRect.width}px`;
331            };
332            positionToggle();
333
334            toggleContainer.append(toggleContents);
335            el.parentElement.prepend(toggleContainer);
336
337            // Process clicks
338            let tocShowing = false;
339            // Allow the caller to control whether this is dismissed
340            // when it is clicked (e.g. sidebar navigation supports
341            // opening and closing the nav tree, so don't dismiss on click)
342            const clickEl = placeholderDescriptor.dismissOnClick
343              ? toggleContainer
344              : toggleTitle;
345
346            const closeToggle = () => {
347              if (tocShowing) {
348                toggleContainer.classList.remove("expanded");
349                toggleContents.style.height = "0px";
350                tocShowing = false;
351              }
352            };
353
354            // Get rid of any expanded toggle if the user scrolls
355            window.document.addEventListener(
356              "scroll",
357              throttle(() => {
358                closeToggle();
359              }, 50)
360            );
361
362            // Handle positioning of the toggle
363            window.addEventListener(
364              "resize",
365              throttle(() => {
366                elRect = undefined;
367                positionToggle();
368              }, 50)
369            );
370
371            window.addEventListener("quarto-hrChanged", () => {
372              elRect = undefined;
373            });
374
375            // Process the click
376            clickEl.onclick = () => {
377              if (!tocShowing) {
378                toggleContainer.classList.add("expanded");
379                toggleContents.style.height = null;
380                tocShowing = true;
381              } else {
382                closeToggle();
383              }
384            };
385          });
386        };
387
388        // Converts a sidebar from a menu back to a sidebar
389        const convertToSidebar = () => {
390          for (const child of el.children) {
391            child.style.opacity = 1;
392            child.style.overflow = null;
393          }
394
395          const placeholderEl = window.document.getElementById(
396            placeholderDescriptor.id
397          );
398          if (placeholderEl) {
399            placeholderEl.remove();
400          }
401
402          el.classList.remove("rollup");
403        };
404
405        if (isReaderMode()) {
406          convertToMenu();
407          isVisible = false;
408        } else {
409          // Find the top and bottom o the element that is being managed
410          const elTop = el.offsetTop;
411          const elBottom =
412            elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
413
414          if (!isVisible) {
415            // If the element is current not visible reveal if there are
416            // no conflicts with overlay regions
417            if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
418              convertToSidebar();
419              isVisible = true;
420            }
421          } else {
422            // If the element is visible, hide it if it conflicts with overlay regions
423            // and insert a placeholder toggle (or if we're in reader mode)
424            if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
425              convertToMenu();
426              isVisible = false;
427            }
428          }
429        }
430      }
431    };
432  };
433
434  // Find any conflicting margin elements and add margins to the
435  // top to prevent overlap
436  const marginChildren = window.document.querySelectorAll(
437    ".column-margin.column-container > * "
438  );
439
440  const layoutMarginEls = () => {
441    let lastBottom = 0;
442    for (const marginChild of marginChildren) {
443      if (marginChild.offsetParent !== null) {
444        // clear the top margin so we recompute it
445        marginChild.style.marginTop = null;
446        const top = marginChild.getBoundingClientRect().top + window.scrollY;
447        if (top < lastBottom) {
448          const margin = lastBottom - top;
449          marginChild.style.marginTop = `${margin}px`;
450        }
451        const styles = window.getComputedStyle(marginChild);
452        const marginTop = parseFloat(styles["marginTop"]);
453        lastBottom =
454          top + marginChild.getBoundingClientRect().height + marginTop;
455      }
456    }
457  };
458  nexttick(layoutMarginEls);
459
460  const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
461  for (const tabEl of tabEls) {
462    const id = tabEl.getAttribute("data-bs-target");
463    if (id) {
464      const columnEl = document.querySelector(
465        `${id} .column-margin, .tabset-margin-content`
466      );
467      if (columnEl)
468        tabEl.addEventListener("shown.bs.tab", function (event) {
469
470          const el = event.srcElement;
471          if (el) {
472            const visibleCls = `${el.id}-margin-content`;
473            // walk up until we find a parent tabset
474            let panelTabsetEl = el.parentElement;
475            while (panelTabsetEl) {
476              if (panelTabsetEl.classList.contains("panel-tabset")) {
477                break;
478              }
479              panelTabsetEl = panelTabsetEl.parentElement;
480            }
481
482            if (panelTabsetEl) {
483              const prevSib = panelTabsetEl.previousElementSibling;
484              if (
485                prevSib &&
486                prevSib.classList.contains("tabset-margin-container")
487              ) {
488                const childNodes = prevSib.querySelectorAll(
489                  ".tabset-margin-content"
490                );
491                for (const childEl of childNodes) {
492                  if (childEl.classList.contains(visibleCls)) {
493                    childEl.classList.remove("collapse");
494                  } else {
495                    childEl.classList.add("collapse");
496                  }
497                }
498              }
499            }
500          }
501
502          layoutMarginEls();
503        });
504    }
505  }
506
507  // Manage the visibility of the toc and the sidebar
508  const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
509    id: "quarto-toc-toggle",
510    titleSelector: "#toc-title",
511    dismissOnClick: true,
512  });
513  const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
514    id: "quarto-sidebarnav-toggle",
515    titleSelector: ".title",
516    dismissOnClick: false,
517  });
518  let tocLeftScrollVisibility;
519  if (leftTocEl) {
520    tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
521      id: "quarto-lefttoc-toggle",
522      titleSelector: "#toc-title",
523      dismissOnClick: true,
524    });
525  }
526
527  // Find the first element that uses formatting in special columns
528  const conflictingEls = window.document.body.querySelectorAll(
529    '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
530  );
531
532  // Filter all the possibly conflicting elements into ones
533  // the do conflict on the left or ride side
534  const arrConflictingEls = Array.from(conflictingEls);
535  const leftSideConflictEls = arrConflictingEls.filter((el) => {
536    if (el.tagName === "ASIDE") {
537      return false;
538    }
539    return Array.from(el.classList).find((className) => {
540      return (
541        className !== "column-body" &&
542        className.startsWith("column-") &&
543        !className.endsWith("right") &&
544        !className.endsWith("container") &&
545        className !== "column-margin"
546      );
547    });
548  });
549  const rightSideConflictEls = arrConflictingEls.filter((el) => {
550    if (el.tagName === "ASIDE") {
551      return true;
552    }
553
554    const hasMarginCaption = Array.from(el.classList).find((className) => {
555      return className == "margin-caption";
556    });
557    if (hasMarginCaption) {
558      return true;
559    }
560
561    return Array.from(el.classList).find((className) => {
562      return (
563        className !== "column-body" &&
564        !className.endsWith("container") &&
565        className.startsWith("column-") &&
566        !className.endsWith("left")
567      );
568    });
569  });
570
571  const kOverlapPaddingSize = 10;
572  function toRegions(els) {
573    return els.map((el) => {
574      const boundRect = el.getBoundingClientRect();
575      const top =
576        boundRect.top +
577        document.documentElement.scrollTop -
578        kOverlapPaddingSize;
579      return {
580        top,
581        bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
582      };
583    });
584  }
585
586  let hasObserved = false;
587  const visibleItemObserver = (els) => {
588    let visibleElements = [...els];
589    const intersectionObserver = new IntersectionObserver(
590      (entries, _observer) => {
591        entries.forEach((entry) => {
592          if (entry.isIntersecting) {
593            if (visibleElements.indexOf(entry.target) === -1) {
594              visibleElements.push(entry.target);
595            }
596          } else {
597            visibleElements = visibleElements.filter((visibleEntry) => {
598              return visibleEntry !== entry;
599            });
600          }
601        });
602
603        if (!hasObserved) {
604          hideOverlappedSidebars();
605        }
606        hasObserved = true;
607      },
608      {}
609    );
610    els.forEach((el) => {
611      intersectionObserver.observe(el);
612    });
613
614    return {
615      getVisibleEntries: () => {
616        return visibleElements;
617      },
618    };
619  };
620
621  const rightElementObserver = visibleItemObserver(rightSideConflictEls);
622  const leftElementObserver = visibleItemObserver(leftSideConflictEls);
623
624  const hideOverlappedSidebars = () => {
625    marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
626    sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
627    if (tocLeftScrollVisibility) {
628      tocLeftScrollVisibility(
629        toRegions(leftElementObserver.getVisibleEntries())
630      );
631    }
632  };
633
634  window.quartoToggleReader = () => {
635    // Applies a slow class (or removes it)
636    // to update the transition speed
637    const slowTransition = (slow) => {
638      const manageTransition = (id, slow) => {
639        const el = document.getElementById(id);
640        if (el) {
641          if (slow) {
642            el.classList.add("slow");
643          } else {
644            el.classList.remove("slow");
645          }
646        }
647      };
648
649      manageTransition("TOC", slow);
650      manageTransition("quarto-sidebar", slow);
651    };
652    const readerMode = !isReaderMode();
653    setReaderModeValue(readerMode);
654
655    // If we're entering reader mode, slow the transition
656    if (readerMode) {
657      slowTransition(readerMode);
658    }
659    highlightReaderToggle(readerMode);
660    hideOverlappedSidebars();
661
662    // If we're exiting reader mode, restore the non-slow transition
663    if (!readerMode) {
664      slowTransition(!readerMode);
665    }
666  };
667
668  const highlightReaderToggle = (readerMode) => {
669    const els = document.querySelectorAll(".quarto-reader-toggle");
670    if (els) {
671      els.forEach((el) => {
672        if (readerMode) {
673          el.classList.add("reader");
674        } else {
675          el.classList.remove("reader");
676        }
677      });
678    }
679  };
680
681  const setReaderModeValue = (val) => {
682    if (window.location.protocol !== "file:") {
683      window.localStorage.setItem("quarto-reader-mode", val);
684    } else {
685      localReaderMode = val;
686    }
687  };
688
689  const isReaderMode = () => {
690    if (window.location.protocol !== "file:") {
691      return window.localStorage.getItem("quarto-reader-mode") === "true";
692    } else {
693      return localReaderMode;
694    }
695  };
696  let localReaderMode = null;
697
698  const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
699  const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
700
701  // Walk the TOC and collapse/expand nodes
702  // Nodes are expanded if:
703  // - they are top level
704  // - they have children that are 'active' links
705  // - they are directly below an link that is 'active'
706  const walk = (el, depth) => {
707    // Tick depth when we enter a UL
708    if (el.tagName === "UL") {
709      depth = depth + 1;
710    }
711
712    // It this is active link
713    let isActiveNode = false;
714    if (el.tagName === "A" && el.classList.contains("active")) {
715      isActiveNode = true;
716    }
717
718    // See if there is an active child to this element
719    let hasActiveChild = false;
720    for (child of el.children) {
721      hasActiveChild = walk(child, depth) || hasActiveChild;
722    }
723
724    // Process the collapse state if this is an UL
725    if (el.tagName === "UL") {
726      if (tocOpenDepth === -1 && depth > 1) {
727        el.classList.add("collapse");
728      } else if (
729        depth <= tocOpenDepth ||
730        hasActiveChild ||
731        prevSiblingIsActiveLink(el)
732      ) {
733        el.classList.remove("collapse");
734      } else {
735        el.classList.add("collapse");
736      }
737
738      // untick depth when we leave a UL
739      depth = depth - 1;
740    }
741    return hasActiveChild || isActiveNode;
742  };
743
744  // walk the TOC and expand / collapse any items that should be shown
745
746  if (tocEl) {
747    walk(tocEl, 0);
748    updateActiveLink();
749  }
750
751  // Throttle the scroll event and walk peridiocally
752  window.document.addEventListener(
753    "scroll",
754    throttle(() => {
755      if (tocEl) {
756        updateActiveLink();
757        walk(tocEl, 0);
758      }
759      if (!isReaderMode()) {
760        hideOverlappedSidebars();
761      }
762    }, 5)
763  );
764  window.addEventListener(
765    "resize",
766    throttle(() => {
767      if (!isReaderMode()) {
768        hideOverlappedSidebars();
769      }
770    }, 10)
771  );
772  hideOverlappedSidebars();
773  highlightReaderToggle(isReaderMode());
774});
775
776// grouped tabsets
777window.addEventListener("pageshow", (_event) => {
778  function getTabSettings() {
779    const data = localStorage.getItem("quarto-persistent-tabsets-data");
780    if (!data) {
781      localStorage.setItem("quarto-persistent-tabsets-data", "{}");
782      return {};
783    }
784    if (data) {
785      return JSON.parse(data);
786    }
787  }
788
789  function setTabSettings(data) {
790    localStorage.setItem(
791      "quarto-persistent-tabsets-data",
792      JSON.stringify(data)
793    );
794  }
795
796  function setTabState(groupName, groupValue) {
797    const data = getTabSettings();
798    data[groupName] = groupValue;
799    setTabSettings(data);
800  }
801
802  function toggleTab(tab, active) {
803    const tabPanelId = tab.getAttribute("aria-controls");
804    const tabPanel = document.getElementById(tabPanelId);
805    if (active) {
806      tab.classList.add("active");
807      tabPanel.classList.add("active");
808    } else {
809      tab.classList.remove("active");
810      tabPanel.classList.remove("active");
811    }
812  }
813
814  function toggleAll(selectedGroup, selectorsToSync) {
815    for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
816      const active = selectedGroup === thisGroup;
817      for (const tab of tabs) {
818        toggleTab(tab, active);
819      }
820    }
821  }
822
823  function findSelectorsToSyncByLanguage() {
824    const result = {};
825    const tabs = Array.from(
826      document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
827    );
828    for (const item of tabs) {
829      const div = item.parentElement.parentElement.parentElement;
830      const group = div.getAttribute("data-group");
831      if (!result[group]) {
832        result[group] = {};
833      }
834      const selectorsToSync = result[group];
835      const value = item.innerHTML;
836      if (!selectorsToSync[value]) {
837        selectorsToSync[value] = [];
838      }
839      selectorsToSync[value].push(item);
840    }
841    return result;
842  }
843
844  function setupSelectorSync() {
845    const selectorsToSync = findSelectorsToSyncByLanguage();
846    Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
847      Object.entries(tabSetsByValue).forEach(([value, items]) => {
848        items.forEach((item) => {
849          item.addEventListener("click", (_event) => {
850            setTabState(group, value);
851            toggleAll(value, selectorsToSync[group]);
852          });
853        });
854      });
855    });
856    return selectorsToSync;
857  }
858
859  const selectorsToSync = setupSelectorSync();
860  for (const [group, selectedName] of Object.entries(getTabSettings())) {
861    const selectors = selectorsToSync[group];
862    // it's possible that stale state gives us empty selections, so we explicitly check here.
863    if (selectors) {
864      toggleAll(selectedName, selectors);
865    }
866  }
867});
868
869function throttle(func, wait) {
870  let waiting = false;
871  return function () {
872    if (!waiting) {
873      func.apply(this, arguments);
874      waiting = true;
875      setTimeout(function () {
876        waiting = false;
877      }, wait);
878    }
879  };
880}
881
882function nexttick(func) {
883  return setTimeout(func, 0);
884}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.