Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions .vitepress/lib/restoreCodeGroupPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
}
return typeof parsed === 'object' ? parsed : {}
}
} catch (e) {
} catch {
// localStorage might not be available or JSON parse failed
}
return {}
Expand All @@ -77,7 +77,7 @@
}
}
keysToRemove.forEach(key => localStorage.removeItem(key))
} catch (e) {
} catch {
// localStorage might not be available
}
}
Expand Down Expand Up @@ -154,24 +154,78 @@
})
}

// VitePress's default scrollOffset (134) accounts for the fixed header
// and padding. This must match VitePress's getScrollOffset() to ensure
// consistent scroll positions between hash-link clicks and page reloads.
const getScrollOffset = () => 134

// Function to scroll to hash (matches VitePress's scrollTo logic)
const scrollToHash = (hash) => {
try {
const target = document.getElementById(decodeURIComponent(hash).slice(1))
if (target) {
const targetPadding = parseInt(window.getComputedStyle(target).paddingTop, 10)
const targetTop = window.scrollY +
target.getBoundingClientRect().top -
getScrollOffset() +
targetPadding

window.scrollTo(0, targetTop)
}
} catch { /* ignore invalid hash */ }
}

const applyToAllCodeGroups = () => {
const codeGroups = document.querySelectorAll('.vp-code-group')
codeGroups.forEach(applyToCodeGroup)
}

// Track if we need to restore hash scroll
const initialHash = window.location.hash
let hashScrollPending = false

if (initialHash) {
// Clear hash to prevent browser's auto-scroll
history.replaceState(null, '', window.location.pathname + window.location.search)
hashScrollPending = true
}

const restoreHashScroll = () => {
if (hashScrollPending) {
// Restore hash and scroll immediately
history.replaceState(null, '', window.location.pathname + window.location.search + initialHash)
// Scroll on next frame to let layout settle
requestAnimationFrame(() => {
scrollToHash(initialHash)
hashScrollPending = false
})
}
}

// Apply immediately to any existing code groups (runs synchronously)
applyToAllCodeGroups()

// If we have code groups and a hash, restore scroll now
if (document.querySelectorAll('.vp-code-group').length > 0) restoreHashScroll()

// Watch for code groups being added dynamically (SPA navigation, HMR in dev mode)
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node instanceof HTMLElement) {
if (node.classList?.contains('vp-code-group')) {
applyToCodeGroup(node)

// This might be the last code group, try to scroll
restoreHashScroll()
} else if (node.querySelector) {
const codeGroups = node.querySelectorAll('.vp-code-group')
codeGroups.forEach(applyToCodeGroup)

// Try to scroll after processing all code groups
if (codeGroups.length > 0) {
restoreHashScroll()
}
}
}
}
Expand All @@ -188,6 +242,11 @@

// Apply again on DOMContentLoaded as safety net
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyToAllCodeGroups)
document.addEventListener('DOMContentLoaded', () => {
applyToAllCodeGroups()

// Final attempt to restore hash scroll if still pending
restoreHashScroll()
})
}
})()
16 changes: 16 additions & 0 deletions .vitepress/lib/useCodeGroupSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,24 @@ function setupEventListeners(): void {
const tabLabel = (label.textContent || '').trim()
if (!tabLabel) return

// Capture the viewport position of the clicked tab before syncing
const clickedRect = label.getBoundingClientRect()

// Sync all code groups with fuzzy matching
syncTabs(tabLabel)

// Restore scroll position to keep the clicked tab in view
requestAnimationFrame(() => {
const newRect = label.getBoundingClientRect()
const scrollDelta = newRect.top - clickedRect.top

if (scrollDelta !== 0) {
window.scrollTo({
top: (window.pageYOffset || document.documentElement.scrollTop) + scrollDelta,
behavior: 'instant'
})
}
})
})
}

Expand Down
Loading