/* ========================================================================= docs.js — tabbed code blocks + copy buttons + scrollspy TOC Vanilla. Each [data-code-tabs] group: buttons swap panels. Each code panel gets a hover copy button. TOC links get aria-current as you scroll. ========================================================================= */ (function () { 'use strict'; /* ---- tabbed code blocks ---- */ function initTabs() { document.querySelectorAll('[data-code-tabs]').forEach((group) => { const buttons = group.querySelectorAll('.code-tabs button'); const panels = group.querySelectorAll('.code-panel'); buttons.forEach((btn) => { btn.addEventListener('click', () => { const target = btn.dataset.tab; buttons.forEach((b) => b.setAttribute('aria-selected', String(b === btn))); panels.forEach((p) => p.setAttribute('data-active', String(p.dataset.tab === target))); }); }); }); } /* ---- copy buttons on code panels ---- */ function initCopy() { document.querySelectorAll('.code-panel').forEach((panel) => { const wrap = panel.closest('.code-wrap'); if (!wrap || wrap.querySelector('.code-copy')) return; const btn = document.createElement('button'); btn.className = 'code-copy'; btn.type = 'button'; btn.textContent = 'Copy'; btn.setAttribute('aria-label', 'Copy code'); btn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(panel.innerText); } catch (e) { const ta = document.createElement('textarea'); ta.value = panel.innerText; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } btn.textContent = 'Copied'; setTimeout(() => (btn.textContent = 'Copy'), 1500); }); wrap.appendChild(btn); }); } /* ---- scrollspy: highlight the TOC link for the section in view ---- */ function initScrollspy() { const links = Array.from(document.querySelectorAll('.doc-toc a')); if (!links.length || !('IntersectionObserver' in window)) return; const map = new Map(); const headings = []; links.forEach((link) => { const id = link.getAttribute('href'); if (!id || !id.startsWith('#')) return; const el = document.querySelector(id); if (el) { map.set(el, link); headings.push(el); } }); const io = new IntersectionObserver( (entries) => { entries.forEach((entry) => { const link = map.get(entry.target); if (!link) return; if (entry.isIntersecting) { links.forEach((l) => l.removeAttribute('aria-current')); link.setAttribute('aria-current', 'true'); } }); }, { rootMargin: '-30% 0px -60% 0px' } ); headings.forEach((h) => io.observe(h)); } function boot() { initTabs(); initCopy(); initScrollspy(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();