1(function () { 2 'use strict'; 3 4 function prefersReducedMotion() { 5 return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 6 } 7 8 /** 9 * Wires up one Table of Contents instance: clicking a link scrolls to 10 * (and moves focus onto) its target heading instead of only jumping 11 * the URL hash, and an IntersectionObserver marks whichever section is 12 * currently in view via aria-current â without an aria-live region, so 13 * it never triggers a screen-reader announcement on every scroll tick. 14 * 15 * @param {HTMLElement} toc The .frbl-toc (or .frbl-toc--collapsible details) root. 16 */ 17 function initToc(toc) { 18 const links = Array.from(toc.querySelectorAll('.frbl-toc__link')); 19 if (!links.length) return; 20 21 const targets = []; 22 links.forEach((link) => { 23 const id = link.getAttribute('href').slice(1); 24 const heading = id ? document.getElementById(id) : null; 25 if (heading) targets.push({ link, heading }); 26 }); 27 28 links.forEach((link) => { 29 link.addEventListener('click', (event) => { 30 const id = link.getAttribute('href').slice(1); 31 const heading = id ? document.getElementById(id) : null; 32 if (!heading) return; 33 34 event.preventDefault(); 35 36 if (window.history && window.history.pushState) { 37 window.history.pushState(null, '', '#' + id); 38 } 39 40 heading.scrollIntoView({ 41 behavior: prefersReducedMotion() ? 'auto' : 'smooth', 42 block: 'start' 43 }); 44 45 // Move focus to the destination so keyboard/screen-reader 46 // users land there too, not just the viewport. 47 heading.focus({ preventScroll: true }); 48 }); 49 }); 50 51 if (!targets.length || typeof IntersectionObserver === 'undefined') return; 52 53 let activeLink = null; 54 55 function setActive(link) { 56 if (activeLink === link) return; 57 if (activeLink) activeLink.removeAttribute('aria-current'); 58 if (link) link.setAttribute('aria-current', 'location'); 59 activeLink = link; 60 } 61 62 const observer = new IntersectionObserver( 63 (entries) => { 64 const visible = entries 65 .filter((entry) => entry.isIntersecting) 66 .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); 67 68 if (visible.length) { 69 const match = targets.find((target) => target.heading === visible[0].target); 70 if (match) setActive(match.link); 71 } 72 }, 73 { rootMargin: '0px 0px -70% 0px', threshold: 0 } 74 ); 75 76 targets.forEach((target) => observer.observe(target.heading)); 77 } 78 79 function init() { 80 document.querySelectorAll('.frbl-toc').forEach(initToc); 81 } 82 83 if (document.readyState === 'loading') { 84 document.addEventListener('DOMContentLoaded', init); 85 } else { 86 init(); 87 } 88})();
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.