docs(ui): center the active nav link in the sidebar instead of edge-clamping
The clamp put deep-linked highlights flush against the sidebar's bottom edge. Centering is simpler AND handles the max-scroll case for free: scrollTop carries the item's center to the scrollport center, and the browser clamps to the scroll extent — items near the nav's top/bottom land as centered as the content allows (scroll pinned fully up/down), which is the requested fallback.
This commit is contained in:
1 parent
da0bae94bb
commit
dd2ab4ea96
1 file changed
+16
-12
+16
-12
@@ -45,23 +45,27 @@ function scrollSidebarToActive(): void {
|
|||||||
const sidebar = document.getElementById("docs-sidebar");
|
const sidebar = document.getElementById("docs-sidebar");
|
||||||
if (!sidebar) return;
|
if (!sidebar) return;
|
||||||
|
|
||||||
// Skip the off-canvas mobile drawer (translated off screen).
|
// The class list ALWAYS carries -translate-x-full on desktop too
|
||||||
if (sidebar.classList.contains("-translate-x-full")) return;
|
// (lg:translate-x-0 overrides it only at the media-query level), so
|
||||||
|
// classList can't tell viewports apart — match the tailwind lg
|
||||||
|
// breakpoint explicitly: below it the sidebar is the off-canvas drawer.
|
||||||
|
if (!window.matchMedia("(min-width: 1024px)").matches) return;
|
||||||
|
|
||||||
const active = sidebar.querySelector<HTMLAnchorElement>(
|
const active = sidebar.querySelector<HTMLAnchorElement>(
|
||||||
`a[href="${location.pathname}"], a[href="${location.pathname.replace(/\.md$/, "")}"]`,
|
`a[href="${location.pathname}"], a[href="${location.pathname.replace(/\.md$/, "")}"]`,
|
||||||
);
|
);
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
|
|
||||||
const sb = sidebar.getBoundingClientRect();
|
// Run once the browser has applied styles and laid the sidebar out.
|
||||||
const ar = active.getBoundingClientRect();
|
requestAnimationFrame(() => {
|
||||||
if (ar.top >= sb.top && ar.bottom <= sb.bottom) return; // already visible
|
const sb = sidebar.getBoundingClientRect();
|
||||||
|
const ar = active.getBoundingClientRect();
|
||||||
if (ar.top < sb.top) {
|
// Center the active item in the sidebar's visible area. The browser
|
||||||
sidebar.scrollTop += ar.top - sb.top;
|
// clamps scrollTop to the scroll extent, so items near the top or
|
||||||
} else {
|
// bottom of the nav land "as centered as possible" (scroll pinned all
|
||||||
sidebar.scrollTop += ar.bottom - sb.bottom;
|
// the way up/down) — no manual edge-clamping needed.
|
||||||
}
|
sidebar.scrollTop += ar.top - sb.top - sb.height / 2 + ar.height / 2;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let docsSearchTimeout: ReturnType<typeof setTimeout> | null = null;
|
let docsSearchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
@@ -153,5 +157,5 @@ Alpine.data("docs", () => ({
|
|||||||
toggleCollapsible,
|
toggleCollapsible,
|
||||||
initializeSearch: initializeDocsSearch,
|
initializeSearch: initializeDocsSearch,
|
||||||
initializeCodeCopyButtons,
|
initializeCodeCopyButtons,
|
||||||
scrollSidebarToActive,
|
scrollSidebarToActive: () => requestAnimationFrame(scrollSidebarToActive),
|
||||||
}));
|
}));
|
||||||
Reference in new issue
Block a user