diff --git a/sphinx_fuma/static/fuma.css b/sphinx_fuma/static/fuma.css index 4100a98..59c2915 100644 --- a/sphinx_fuma/static/fuma.css +++ b/sphinx_fuma/static/fuma.css @@ -799,8 +799,7 @@ body[data-toc-style="clerk"] .fd-toc-item a { transition: color 0.15s, opacity 0.15s; } -body[data-toc-style="clerk"] .fd-toc-item.fd-active a, -body[data-toc-style="clerk"] .fd-toc-item.fd-visible a { +body[data-toc-style="clerk"] .fd-toc-item.fd-active a { opacity: 1; } @@ -1144,6 +1143,11 @@ body[data-toc-style="clerk"] .fd-toc-item.fd-visible a { line-height: 1.75; } +.fd-prose ul.simple > li > p, +.fd-prose ol.simple > li > p { + margin: 0; +} + .fd-prose a { color: var(--color-fd-foreground); font-weight: 500; diff --git a/sphinx_fuma/static/fuma.js b/sphinx_fuma/static/fuma.js index 43c517c..729b63f 100644 --- a/sphinx_fuma/static/fuma.js +++ b/sphinx_fuma/static/fuma.js @@ -162,7 +162,8 @@ var targets = tocItems .map(function (item) { var href = item.querySelector("a").getAttribute("href") || ""; - var heading = href.charAt(0) === "#" ? document.getElementById(decodeURIComponent(href.slice(1))) : null; + var anchor = href.charAt(0) === "#" ? document.getElementById(decodeURIComponent(href.slice(1))) : null; + var heading = anchor && (anchor.matches("h1, h2, h3, h4, h5, h6") ? anchor : anchor.querySelector("h1, h2, h3, h4, h5, h6")); return heading ? { item: item, heading: heading } : null; }) .filter(Boolean); @@ -170,13 +171,12 @@ // The thumb spans every heading currently on screen, not just the first. var mark = function (active) { tocItems.forEach(function (item) { - item.classList.remove("fd-active", "fd-visible"); + item.classList.remove("fd-active"); }); if (!active.length) return; active.forEach(function (entry) { - entry.item.classList.add("fd-visible"); + entry.item.classList.add("fd-active"); }); - active[0].item.classList.add("fd-active"); if (rail) { var first = active[0].item.querySelector("a"); var last = active[active.length - 1].item.querySelector("a"); @@ -199,15 +199,21 @@ return visible.has(target.heading); }); if (!active.length) { - // Nothing in the band: fall back to the last heading scrolled past. - var passed = targets.filter(function (target) { - return target.heading.getBoundingClientRect().top < 0; + var viewTop = entries[0] && entries[0].rootBounds ? entries[0].rootBounds.top : 0; + var fallback = null; + var fallbackDistance = Infinity; + targets.forEach(function (target) { + var distance = Math.abs(target.heading.getBoundingClientRect().top - viewTop); + if (distance < fallbackDistance) { + fallback = target; + fallbackDistance = distance; + } }); - active = passed.length ? [passed[passed.length - 1]] : [targets[0]]; + if (fallback) active = [fallback]; } mark(active); }, - { rootMargin: "-80px 0px -60% 0px", threshold: 0 } + { threshold: 0.9 } ); targets.forEach(function (target) { diff --git a/sphinx_fuma/tests/test_theme.py b/sphinx_fuma/tests/test_theme.py index 5bbb1a3..8238f00 100644 --- a/sphinx_fuma/tests/test_theme.py +++ b/sphinx_fuma/tests/test_theme.py @@ -205,6 +205,21 @@ def test_toc_rail_is_an_svg_with_track_and_thumb(self, built): assert 'class="fd-toc-track"' in html assert 'class="fd-toc-thumb"' in html + def test_toc_scroll_spy_observes_headings_across_the_viewport(self, built): + script = (built / "_static" / "fuma.js").read_text() + assert 'anchor.querySelector("h1, h2, h3, h4, h5, h6")' in script + assert "threshold: 0.9" in script + assert "rootMargin:" not in script + + def test_toc_scroll_spy_marks_every_visible_item_active(self, built): + script = (built / "_static" / "fuma.js").read_text() + assert 'entry.item.classList.add("fd-active")' in script + assert 'active[0].item.classList.add("fd-active")' not in script + + def test_tight_list_paragraphs_do_not_expand_item_spacing(self, built): + stylesheet = (built / "_static" / "fuma.css").read_text() + assert (".fd-prose ul.simple > li > p,\n.fd-prose ol.simple > li > p {\n margin: 0;\n}") in stylesheet + class TestSteps: def test_directive_wraps_content_in_a_steps_container(self, built):