Files
robonen 8adc2522c6 docs: site WIP, extractor type cleanup, tests preset; add broadcastedRef
Type the docs extractor's package.json parsing as unknown; comment the Vite
plugin version-skew cast; wire the tests preset; site/architecture WIP.
2026-06-15 16:55:22 +07:00

71 lines
1.7 KiB
Vue

<script setup lang="ts">interface TocItem {
id: string;
text: string;
depth: number;
}
const props = defineProps<{
items: TocItem[];
}>();
const activeId = ref<string>('');
let observer: IntersectionObserver | null = null;
function setup() {
if (!import.meta.client || props.items.length === 0) return;
observer?.disconnect();
observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) activeId.value = entry.target.id;
}
},
{ rootMargin: '0px 0px -75% 0px', threshold: 0 },
);
for (const item of props.items) {
const el = document.getElementById(item.id);
if (el) observer.observe(el);
}
}
onMounted(() => nextTick(setup));
watch(() => props.items, () => nextTick(setup));
onUnmounted(() => observer?.disconnect());
function go(id: string) {
const el = document.getElementById(id);
if (el) {
activeId.value = id;
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
history.replaceState(null, '', `#${id}`);
}
}
</script>
<template>
<nav v-if="items.length > 0" class="text-sm">
<div class="comment-label mb-3">
on this page
</div>
<ul class="space-y-1 border-l border-border">
<li v-for="item in items" :key="item.id">
<a
:href="`#${item.id}`"
:class="[
'block py-1 -ml-px border-l-2 transition-colors',
item.depth === 3 ? 'pl-6' : 'pl-4',
activeId === item.id
? 'border-accent text-accent-text font-medium'
: 'border-transparent text-fg-muted hover:text-fg',
]"
@click.prevent="go(item.id)"
>
{{ item.text }}
</a>
</li>
</ul>
</nav>
</template>