|
8 | 8 | function getLargestElement(elements: NodeListOf<Element>): Element | null { |
9 | 9 | let largestArea = 0; |
10 | 10 | let largestElement: Element | null = null; |
| 11 | + let firstFullyVisibleElement: Element | null = null; |
11 | 12 |
|
12 | 13 | for (const element of elements) { |
13 | 14 | const rect = element.getBoundingClientRect(); |
|
19 | 20 | if (visibleWidth > 0 && visibleHeight > 0) { |
20 | 21 | const area = visibleWidth * visibleHeight; |
21 | 22 |
|
| 23 | + // Check if element is fully visible |
| 24 | + const isFullyVisible = |
| 25 | + rect.top >= 0 && |
| 26 | + rect.left >= 0 && |
| 27 | + rect.bottom <= window.innerHeight && |
| 28 | + rect.right <= window.innerWidth; |
| 29 | +
|
| 30 | + // Prioritize first fully visible element |
| 31 | + if (isFullyVisible && !firstFullyVisibleElement) { |
| 32 | + firstFullyVisibleElement = element; |
| 33 | + } |
| 34 | +
|
| 35 | + // Track largest element as fallback |
22 | 36 | if (area > largestArea) { |
23 | 37 | largestArea = area; |
24 | 38 | largestElement = element; |
25 | 39 | } |
26 | 40 | } |
27 | 41 | } |
28 | 42 |
|
29 | | - return largestElement; |
| 43 | + // Return first fully visible element if found, otherwise largest element |
| 44 | + return firstFullyVisibleElement || largestElement; |
| 45 | + } |
| 46 | +
|
| 47 | + function scrollToElement(elementId: string) { |
| 48 | + const target = document.getElementById(elementId); |
| 49 | + if (target) { |
| 50 | + // Update URL hash |
| 51 | + window.history.pushState(null, "", `#${elementId}`); |
| 52 | + // Scroll to target |
| 53 | + target.scrollIntoView({ block: "start" }); |
| 54 | + } |
30 | 55 | } |
31 | 56 |
|
32 | | - function listenLargestSnippetOnScroll(): () => void { |
33 | | - function onScroll(): void { |
| 57 | + onMount(function listenLargestSnippetOnScroll() { |
| 58 | + function onScroll() { |
34 | 59 | const largestSnippet = getLargestElement( |
35 | 60 | document.querySelectorAll("[data-snippet-id]"), |
36 | 61 | ); |
|
49 | 74 | return () => { |
50 | 75 | document.removeEventListener("scroll", throttleOnScroll); |
51 | 76 | }; |
52 | | - } |
53 | | -
|
54 | | - let unlistenLargestSnippetOnScroll: (() => void) | undefined; |
55 | | -
|
56 | | - onMount(() => { |
57 | | - unlistenLargestSnippetOnScroll = listenLargestSnippetOnScroll(); |
58 | | -
|
59 | | - return () => unlistenLargestSnippetOnScroll?.(); |
60 | 77 | }); |
61 | 78 | </script> |
62 | 79 |
|
|
67 | 84 | <ul class="space-y-6"> |
68 | 85 | {#each sections as section (section.sectionId)} |
69 | 86 | <li> |
70 | | - <a |
71 | | - href={`#${section.sectionId}`} |
| 87 | + <button |
72 | 88 | class={[ |
73 | | - "inline-block w-full py-1.5 px-4 text-white font-semibold opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity", |
| 89 | + "inline-block w-full py-1.5 px-4 text-white font-semibold opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity text-left", |
74 | 90 | { |
75 | 91 | "bg-gray-800": |
76 | 92 | largestVisibleSnippetId && |
77 | 93 | largestVisibleSnippetId.startsWith(section.sectionId), |
78 | 94 | }, |
79 | 95 | ]} |
| 96 | + onclick={() => scrollToElement(section.sectionId)} |
80 | 97 | > |
81 | 98 | {section.title} |
82 | | - </a> |
| 99 | + </button> |
83 | 100 | <ul> |
84 | 101 | {#each snippets.filter((s: any) => s.sectionId === section.sectionId) as snippet (snippet.snippetId)} |
85 | 102 | {@const snippetPathId = |
86 | 103 | section.sectionId + "." + snippet.snippetId} |
87 | 104 | <li> |
88 | | - <a |
89 | | - href={`#${snippetPathId}`} |
| 105 | + <button |
90 | 106 | class={[ |
91 | | - "inline-block w-full py-1.5 px-4 text-white font-medium hover:bg-gray-800 rounded hover:opacity-100 transition-opacity", |
| 107 | + "inline-block w-full py-1.5 px-4 text-white font-medium hover:bg-gray-800 rounded hover:opacity-100 transition-opacity text-left", |
92 | 108 | snippetPathId === largestVisibleSnippetId |
93 | 109 | ? "bg-gray-800 opacity-70" |
94 | 110 | : "opacity-50", |
95 | 111 | ]} |
| 112 | + onclick={() => scrollToElement(snippetPathId)} |
96 | 113 | > |
97 | 114 | {snippet.title} |
98 | | - </a> |
| 115 | + </button> |
99 | 116 | </li> |
100 | 117 | {/each} |
101 | 118 | </ul> |
|
0 commit comments