Files
archicratie-edition/src/components/LocalToc.astro
archicratia 60d88939b0
All checks were successful
CI / build-and-anchors (push) Successful in 1m25s
SMOKE / smoke (push) Successful in 11s
CI / build-and-anchors (pull_request) Successful in 1m20s
Seed from NAS prod snapshot 20260130-190531
2026-01-31 10:51:38 +00:00

183 lines
4.6 KiB
Plaintext

---
const { headings } = Astro.props;
// H2/H3 seulement
const items = (headings || []).filter((h) => h.depth >= 2 && h.depth <= 3);
---
{items.length > 0 && (
<nav class="toc-local" aria-label="Dans ce chapitre">
<div class="toc-local__title">Dans ce chapitre</div>
<ol class="toc-local__list">
{items.map((h) => (
<li
class={`toc-local__item d${h.depth}`}
data-toc-item
data-depth={h.depth}
data-id={h.slug}
>
<a href={`#${h.slug}`} data-toc-link data-slug={h.slug}>
{h.text}
</a>
</li>
))}
</ol>
</nav>
)}
<script is:inline>
(() => {
function init() {
const toc = document.querySelector(".toc-local");
if (!toc) return;
const itemEls = Array.from(toc.querySelectorAll("[data-toc-item]"));
if (!itemEls.length) return;
const ordered = itemEls
.map((li) => {
const a = li.querySelector("a[data-toc-link]");
const id = li.getAttribute("data-id") || a?.dataset.slug || "";
const depth = Number(li.getAttribute("data-depth") || "0");
const el = id ? document.getElementById(id) : null; // span.details-anchor OU h3[id]
return (a && id && el) ? { id, depth, li, a, el } : null;
})
.filter(Boolean);
if (!ordered.length) return;
const clear = () => {
for (const t of ordered) {
t.a.removeAttribute("aria-current");
t.li.classList.remove("is-current");
}
};
const openDetailsIfNeeded = (el) => {
const d = el?.closest?.("details");
if (d && !d.open) d.open = true;
};
let current = "";
const setCurrent = (id) => {
if (!id || id === current) return;
const t = ordered.find((x) => x.id === id);
if (!t) return;
current = id;
clear();
openDetailsIfNeeded(t.el);
t.a.setAttribute("aria-current", "true");
t.li.classList.add("is-current");
// ✅ IMPORTANT: plus de scrollIntoView ici
// sinon ça scroll l'aside pendant le scroll du reading => TOC global “disparaît”.
};
const computeActive = () => {
const visible = ordered.filter((t) => {
const d = t.el.closest?.("details");
if (d && !d.open) {
// Si l'élément est dans <summary>, il reste visible même details fermé
const inSummary = !!t.el.closest?.("summary");
if (!inSummary) return false;
}
return true;
});
if (!visible.length) return;
const y = 120;
let best = null;
for (const t of visible) {
const r = t.el.getBoundingClientRect();
if (!r || (r.width === 0 && r.height === 0)) continue;
if (r.top <= y) best = t;
else break;
}
if (!best) best = visible[0];
setCurrent(best.id);
};
let ticking = false;
const onScroll = () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
ticking = false;
computeActive();
});
};
const syncFromHash = () => {
const id = (location.hash || "").slice(1);
if (!id) { computeActive(); return; }
const el = document.getElementById(id);
if (el) openDetailsIfNeeded(el);
setCurrent(id);
};
toc.addEventListener("click", (ev) => {
const a = ev.target?.closest?.("a[data-toc-link]");
if (!a) return;
const id = a.dataset.slug || "";
if (!id) return;
const el = document.getElementById(id);
if (el) openDetailsIfNeeded(el);
setCurrent(id);
});
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
window.addEventListener("hashchange", syncFromHash);
syncFromHash();
onScroll();
}
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", init, { once: true });
} else {
init();
}
})();
</script>
<style>
.toc-local{margin-top:12px;border:1px solid rgba(127,127,127,.25);border-radius:16px;padding:12px}
.toc-local__title{font-size:13px;opacity:.85;margin-bottom:8px}
.toc-local__list{list-style:none;margin:0;padding:0}
.toc-local__item::marker{content:""}
.toc-local__item{margin:6px 0}
.toc-local__item.d3{margin-left:12px;opacity:.9}
.toc-local__item.is-current > a{
font-weight: 750;
text-decoration: underline;
}
.toc-local a{
display:inline-block;
max-width:100%;
text-decoration:none;
}
.toc-local a:hover{ text-decoration: underline; }
.toc-local__list{
max-height: 44vh;
overflow: auto;
padding-right: 8px;
scrollbar-gutter: stable;
}
</style>