fixed scrollspy

This commit is contained in:
Sergey Chernov 2025-12-22 06:30:40 +01:00
parent 0add0ab54c
commit 0dbfb473bf
3 changed files with 11 additions and 7 deletions

View File

@ -181,7 +181,7 @@ fun DocsPage(
scheduled = false scheduled = false
try { try {
val heads = toc.mapNotNull { id -> el.querySelector("#${id.id}") as? HTMLHeadingElement } val heads = toc.mapNotNull { id -> el.querySelector("#${id.id}") as? HTMLHeadingElement }
val tops = heads.map { it.getBoundingClientRect().top + window.scrollY } val tops = heads.map { it.getBoundingClientRect().top }
val offset = (updateNavbarOffsetVar() + 16).toDouble() val offset = (updateNavbarOffsetVar() + 16).toDouble()
val idx = activeIndexForTops(tops, offset) val idx = activeIndexForTops(tops, offset)
setActiveTocId(if (idx in toc.indices) toc[idx].id else null) setActiveTocId(if (idx in toc.indices) toc[idx].id else null)

View File

@ -1024,11 +1024,15 @@ internal fun normalizePath(path: String): String {
// If none are above the offset, returns 0. If list is empty, returns 0. // If none are above the offset, returns 0. If list is empty, returns 0.
fun activeIndexForTops(tops: List<Double>, offsetPx: Double): Int { fun activeIndexForTops(tops: List<Double>, offsetPx: Double): Int {
if (tops.isEmpty()) return 0 if (tops.isEmpty()) return 0
var lastAbove = 0
for (i in tops.indices) { for (i in tops.indices) {
if (tops[i] - offsetPx > 0.0) return i if (tops[i] <= offsetPx) {
lastAbove = i
} else {
break
} }
// If all headings are above the offset, select the last one }
return tops.size - 1 return lastAbove
} }
fun main() { fun main() {

View File

@ -28,20 +28,20 @@ class ScrollSpyTest {
fun selectsFirstWhenOnlyFirstIsAboveOffset() { fun selectsFirstWhenOnlyFirstIsAboveOffset() {
val tops = listOf(20.0, 200.0, 800.0) // px from viewport top val tops = listOf(20.0, 200.0, 800.0) // px from viewport top
val idx = activeIndexForTops(tops, offsetPx = 80.0) val idx = activeIndexForTops(tops, offsetPx = 80.0)
assertEquals(1, idx) // 20 <= 80, 200 > 80 -> index 1 (second heading is first below offset) assertEquals(0, idx) // 20 <= 80 (active), 200 > 80 (stops) -> index 0
} }
@Test @Test
fun selectsLastHeadingAboveOffset() { fun selectsLastHeadingAboveOffset() {
val tops = listOf(-100.0, 50.0, 70.0) val tops = listOf(-100.0, 50.0, 70.0)
val idx = activeIndexForTops(tops, offsetPx = 80.0) val idx = activeIndexForTops(tops, offsetPx = 80.0)
assertEquals(2, idx) // all three are <= 80 -> last index assertEquals(2, idx) // all three are <= 80 -> last index 2
} }
@Test @Test
fun stopsBeforeFirstBelowOffset() { fun stopsBeforeFirstBelowOffset() {
val tops = listOf(-200.0, -50.0, 30.0, 150.0, 400.0) val tops = listOf(-200.0, -50.0, 30.0, 150.0, 400.0)
val idx = activeIndexForTops(tops, offsetPx = 80.0) val idx = activeIndexForTops(tops, offsetPx = 80.0)
assertEquals(3, idx) // 30 <= 80 qualifies; 150 > 80 stops, so index 3rd (0-based -> 3?) assertEquals(2, idx) // 30 <= 80 is the last one; 150 > 80 -> index 2
} }
} }