Jake Vanderwerf
2026-05-31 d7e7d248cbe41cd7a9ef9c2fb022b6c4831f99a3
build/glossary/view.js
@@ -1 +1,165 @@
(()=>{class t{constructor(t="dl.glossary",e="nav.glossary-index"){this.glossary=document.querySelector(t),this.nav=document.querySelector(e),this.glossary&&this.nav&&(this.terms=this.glossary.querySelectorAll("dt[id]"),this.navList=this.nav.querySelector("ul"),this.activeClass="active",this.currentActive=null,this.init(),this.setupResizeHandler())}init(){const t={root:null,rootMargin:this.getRootMargin(),threshold:0};this.observer=new IntersectionObserver((t=>this.handleIntersection(t)),t),this.terms.forEach((t=>this.observer.observe(t))),this.handleScroll=this.debounce((()=>this.checkActiveTerm()),100),window.addEventListener("scroll",this.handleScroll,{passive:!0})}getRootMargin(){return"-50% 0px -50% 0px"}setupResizeHandler(){let t;window.addEventListener("resize",(()=>{clearTimeout(t),t=setTimeout((()=>{this.reinitialize()}),250)}))}reinitialize(){this.observer&&this.observer.disconnect(),this.init()}handleIntersection(t){const e=t.find((t=>t.isIntersecting));e&&this.setActive(e.target)}checkActiveTerm(){parseFloat(getComputedStyle(document.documentElement).fontSize);let t=null,e=1/0;this.terms.forEach((i=>{const s=i.getBoundingClientRect();if(s.top+s.height/2>=0&&s.top+s.height/2<=window.innerHeight){const n=window.innerHeight/2,r=Math.abs(s.top-n);r<e&&(e=r,t=i)}})),t&&this.setActive(t)}setActive(t){this.currentActive!==t&&(this.currentActive&&this.currentActive.classList.remove(this.activeClass),t.classList.add(this.activeClass),this.currentActive=t,this.updateNavigation(t.id))}updateNavigation(t){this.nav.querySelectorAll("a").forEach((t=>t.classList.remove(this.activeClass)));const e=this.nav.querySelector(`a[href="#${t}"]`);e&&(e.classList.add(this.activeClass),this.centerNavItem(e))}centerNavItem(t){const e=this.navList.getBoundingClientRect(),i=t.getBoundingClientRect(),s=this.navList.scrollTop,n=i.top-e.top,r=e.height/2-i.height/2;this.navList.scrollTo({top:s+n-r,behavior:"smooth"})}debounce(t,e){let i;return function(...s){clearTimeout(i),i=setTimeout((()=>{clearTimeout(i),t(...s)}),e)}}destroy(){this.observer&&this.observer.disconnect(),window.removeEventListener("scroll",this.handleScroll)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",(()=>{new t})):new t})();
/******/ (() => { // webpackBootstrap
/*!******************************!*\
  !*** ./src/glossary/view.js ***!
  \******************************/
/**
 * Glossary Navigation Active State Manager
 * Handles highlighting active terms as they scroll into view
 * and syncing navigation with scroll position
 */
class GlossaryNavigator {
  constructor(glossarySelector = 'dl.glossary', navSelector = 'nav.glossary-index') {
    this.glossary = document.querySelector(glossarySelector);
    this.nav = document.querySelector(navSelector);
    if (!this.glossary || !this.nav) return;
    this.terms = this.glossary.querySelectorAll('dt[id]');
    this.navList = this.nav.querySelector('ul');
    this.activeClass = 'active';
    this.currentActive = null;
    this.init();
    this.setupResizeHandler();
  }
  init() {
    // Set up Intersection Observer with screen-size appropriate margins
    const observerOptions = {
      root: null,
      // viewport
      rootMargin: this.getRootMargin(),
      threshold: 0
    };
    this.observer = new IntersectionObserver(entries => this.handleIntersection(entries), observerOptions);
    // Observe all terms
    this.terms.forEach(term => this.observer.observe(term));
    // Also handle manual scroll for edge cases
    this.handleScroll = this.debounce(() => this.checkActiveTerm(), 100);
    window.addEventListener('scroll', this.handleScroll, {
      passive: true
    });
  }
  getRootMargin() {
    // On larger screens: centered (50% from top and bottom)
    return '-50% 0px -50% 0px';
  }
  setupResizeHandler() {
    let resizeTimer;
    window.addEventListener('resize', () => {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(() => {
        // Reinitialize observer with new margins on resize
        this.reinitialize();
      }, 250);
    });
  }
  reinitialize() {
    // Disconnect old observer
    if (this.observer) {
      this.observer.disconnect();
    }
    // Create new observer with updated margins
    this.init();
  }
  handleIntersection(entries) {
    // Find the entry that's intersecting
    const intersecting = entries.find(entry => entry.isIntersecting);
    if (intersecting) {
      this.setActive(intersecting.target);
    }
  }
  checkActiveTerm() {
    // Fallback method to find which term is in the trigger zone
    const remInPixels = parseFloat(getComputedStyle(document.documentElement).fontSize);
    const margin = remInPixels * 4;
    let closestTerm = null;
    let closestDistance = Infinity;
    this.terms.forEach(term => {
      const rect = term.getBoundingClientRect();
      const isInZone = rect.top + rect.height / 2 >= 0 && rect.top + rect.height / 2 <= window.innerHeight;
      if (isInZone) {
        // Find closest to the trigger point
        const triggerPoint = window.innerHeight / 2;
        const distance = Math.abs(rect.top - triggerPoint);
        if (distance < closestDistance) {
          closestDistance = distance;
          closestTerm = term;
        }
      }
    });
    if (closestTerm) {
      this.setActive(closestTerm);
    }
  }
  setActive(term) {
    if (this.currentActive === term) return;
    // Remove active class from previous term
    if (this.currentActive) {
      this.currentActive.classList.remove(this.activeClass);
    }
    // Add active class to current term
    term.classList.add(this.activeClass);
    this.currentActive = term;
    // Update navigation
    this.updateNavigation(term.id);
  }
  updateNavigation(termId) {
    // Remove active from all nav links
    const navLinks = this.nav.querySelectorAll('a');
    navLinks.forEach(link => link.classList.remove(this.activeClass));
    // Find and activate corresponding nav link
    const activeLink = this.nav.querySelector(`a[href="#${termId}"]`);
    if (activeLink) {
      activeLink.classList.add(this.activeClass);
      // Scroll the nav list to center the active link
      this.centerNavItem(activeLink);
    }
  }
  centerNavItem(link) {
    const listRect = this.navList.getBoundingClientRect();
    const linkRect = link.getBoundingClientRect();
    // Calculate position to center the link in the nav container
    const scrollTop = this.navList.scrollTop;
    const linkOffset = linkRect.top - listRect.top;
    const centerOffset = listRect.height / 2 - linkRect.height / 2;
    this.navList.scrollTo({
      top: scrollTop + linkOffset - centerOffset,
      behavior: 'smooth'
    });
  }
  debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }
  destroy() {
    if (this.observer) {
      this.observer.disconnect();
    }
    window.removeEventListener('scroll', this.handleScroll);
  }
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => {
    new GlossaryNavigator();
  });
} else {
  new GlossaryNavigator();
}
/******/ })()
;
//# sourceMappingURL=view.js.map