labs / static /js /docs.js
3v324v23's picture
deploy: unified router + dreamy website (2026-06-16T09:46:52Z)
c1a683f
Raw
History Blame Contribute Delete
3.16 kB
/* =========================================================================
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(); }
})();