PageSourceSearch

https://davidperezgar.com/wp-content/plugins/frontblocks/assets/ta…/frontblocks-toc-frontend.js?ver=1.5.4

js davidperezgar.com collected 2026-09-25 21:09:35 UTC 3,184 bytes, 88 lines download raw bytes

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.