Skip to content

Commit 35445f1

Browse files
authored
fix(TOC): indicator positioning (#55)
1 parent 77adb5b commit 35445f1

File tree

4 files changed

+59
-11
lines changed

4 files changed

+59
-11
lines changed

.changeset/tall-bobcats-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@svecodocs/kit": patch
3+
---
4+
5+
fix: TOC indicator

docs/src/content/components/prop-field.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ Configuration options to customize the behavior of the `Checkbox` component.
7070
</Collapsible>
7171
</PropField>
7272

73+
## Some Really Long Title That will Wrap
74+
7375
## Props
7476

7577
<PropField name="name" type="string" required>

packages/kit/src/lib/components/toc/desktop-toc.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
<List class="size-4 shrink-0" />
1717
<p class="text-muted-foreground text-sm">On this page</p>
1818
</div>
19-
<div class="relative mt-4">
19+
<div class="relative mt-4" data-toc-container>
2020
<div
21-
class="bg-foreground absolute -left-[0.5px] top-0 h-5 w-0.5 rounded-full"
22-
style="transition: top 0.25s; top:{tocState.markerTopStyle};"
21+
class="bg-foreground absolute -left-[0.5px] top-0 w-0.5 rounded-full"
22+
style="transition: top 0.25s, height 0.25s; top:{tocState.markerTopStyle}; height:{tocState.markerHeightStyle};"
2323
></div>
2424
<TocTree {tree} {tocState} />
2525
</div>

packages/kit/src/lib/hooks/use-toc.svelte.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { untrack } from "svelte";
2-
import { page } from "$app/stores";
3-
import { fromStore } from "svelte/store";
2+
import { page } from "$app/state";
43
import { addEventListener } from "svelte-toolbelt";
54

65
export type TocItem = {
@@ -16,14 +15,53 @@ export type TableOfContents = {
1615
export function useToc(getItemIds: () => string[]) {
1716
const itemIds = $derived(getItemIds());
1817
let activeId = $state<string | null>(null);
19-
const pageState = fromStore(page);
20-
const urlHash = $derived(pageState.current.url.hash);
18+
const urlHash = $derived(page.url.hash);
2119
const isAtBottom = useIsAtBottom();
2220

23-
const activeIndex = $derived(itemIds.findIndex((id) => id === activeId));
24-
const markerTopStyle = $derived.by(() => {
25-
if (activeIndex === -1) return "0px";
26-
return activeIndex * 28 + "px";
21+
let markerTopStyle = $state("0px");
22+
let markerHeightStyle = $state("20px");
23+
24+
$effect(() => {
25+
if (!activeId) {
26+
markerTopStyle = "0px";
27+
return;
28+
}
29+
30+
requestAnimationFrame(() => {
31+
const tocContainer = document.querySelector("[data-toc-container]");
32+
if (!tocContainer) return;
33+
34+
const tocLinks = Array.from(tocContainer.querySelectorAll("a[href]"));
35+
36+
let targetIndex = -1;
37+
let targetElement: Element | null = null;
38+
for (let i = 0; i < tocLinks.length; i++) {
39+
const href = tocLinks[i].getAttribute("href");
40+
if (href && href.includes(`#${activeId}`)) {
41+
targetIndex = i;
42+
targetElement = tocLinks[i];
43+
break;
44+
}
45+
}
46+
47+
if (targetIndex === -1 || !targetElement) {
48+
const oldIndex = itemIds.findIndex((id) => id === activeId);
49+
markerTopStyle = oldIndex * 28 + "px";
50+
markerHeightStyle = "20px";
51+
return;
52+
}
53+
54+
let totalHeight = 0;
55+
for (let i = 0; i < targetIndex; i++) {
56+
totalHeight += tocLinks[i].getBoundingClientRect().height;
57+
}
58+
59+
const activeElementHeight = targetElement.getBoundingClientRect().height;
60+
61+
markerTopStyle = totalHeight + "px";
62+
const isLastItem = targetIndex === tocLinks.length - 1;
63+
markerHeightStyle = activeElementHeight - (isLastItem ? 0 : 8) + "px";
64+
});
2765
});
2866

2967
function isActive(item: TocItem) {
@@ -91,6 +129,9 @@ export function useToc(getItemIds: () => string[]) {
91129
get markerTopStyle() {
92130
return markerTopStyle;
93131
},
132+
get markerHeightStyle() {
133+
return markerHeightStyle;
134+
},
94135
isActive,
95136
isLastItem,
96137
};

0 commit comments

Comments
 (0)