@readme/markdown 13.8.0 → 13.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,10 @@ function buildLinkMap(nav: HTMLElement) {
17
17
  return map;
18
18
  }
19
19
 
20
+ const VISIBLE_RATIO = 0.4;
21
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
22
+ const SCROLL_BOTTOM_TOLERANCE = 1;
23
+
20
24
  /**
21
25
  * Watches headings in the viewport and toggles `active` on the
22
26
  * corresponding TOC links so the reader always knows where they are.
@@ -44,8 +48,12 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
44
48
  if (headings.length === 0) return undefined;
45
49
 
46
50
  let activeId: string | null = null;
51
+ let clickLocked = false;
47
52
  const visible = new Set<string>();
48
53
 
54
+ const isAtBottom = () =>
55
+ window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
56
+
49
57
  const activate = (id: string | null) => {
50
58
  if (id === activeId) return;
51
59
  if (activeId) linkMap.get(activeId)?.forEach(a => a.classList.remove('active'));
@@ -66,22 +74,67 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
66
74
  }
67
75
  };
68
76
 
77
+ const updateActive = () => {
78
+ if (clickLocked) return;
79
+
80
+ if (isAtBottom()) {
81
+ activate(headings[headings.length - 1].id);
82
+ return;
83
+ }
84
+
85
+ const topmost = headings.find(el => visible.has(el.id));
86
+ if (topmost) activate(topmost.id);
87
+ };
88
+
69
89
  const observer = new IntersectionObserver(
70
90
  entries => {
71
91
  entries.forEach(e => {
72
92
  if (e.isIntersecting) visible.add(e.target.id);
73
93
  else visible.delete(e.target.id);
74
94
  });
75
- // Highlight the topmost visible heading; if none are visible,
76
- // keep the last active one so the user still has context.
77
- const topmost = headings.find(el => visible.has(el.id));
78
- if (topmost) activate(topmost.id);
95
+ updateActive();
79
96
  },
80
- { rootMargin: '0px 0px -60% 0px', threshold: 0 },
97
+ { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 },
81
98
  );
82
99
 
100
+ // Also check on scroll so bottom-of-page detection works even when
101
+ // no headings are crossing the intersection boundary.
102
+ const onScroll = () => { updateActive(); };
103
+
104
+ // Click a ToC link → immediately activate it, suppress the observer
105
+ // until the smooth scroll finishes, then hand control back.
106
+ const onClick = (e: MouseEvent) => {
107
+ const anchor = (e.target as HTMLElement).closest?.('a[href^="#"]');
108
+ if (!anchor) return;
109
+ const id = decodeURIComponent(anchor.getAttribute('href')!.slice(1));
110
+ if (!linkMap.has(id)) return;
111
+
112
+ e.preventDefault();
113
+ activate(id);
114
+ clickLocked = true;
115
+
116
+ const unlock = () => { clickLocked = false; };
117
+ window.addEventListener('scrollend', unlock, { once: true });
118
+
119
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
120
+ };
121
+
83
122
  headings.forEach(el => { observer.observe(el); });
84
- return () => { observer.disconnect(); };
123
+ window.addEventListener('scroll', onScroll, { passive: true });
124
+ nav.addEventListener('click', onClick);
125
+
126
+ // Set initial active state for the first heading visible in the viewport
127
+ const initialHeading = headings.find(el => {
128
+ const rect = el.getBoundingClientRect();
129
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
130
+ });
131
+ if (initialHeading) activate(initialHeading.id);
132
+
133
+ return () => {
134
+ observer.disconnect();
135
+ window.removeEventListener('scroll', onScroll);
136
+ nav.removeEventListener('click', onClick);
137
+ };
85
138
  }, [navRef, linkCount]);
86
139
  }
87
140
 
package/dist/main.js CHANGED
@@ -12087,6 +12087,9 @@ function buildLinkMap(nav) {
12087
12087
  });
12088
12088
  return map;
12089
12089
  }
12090
+ const VISIBLE_RATIO = 0.4;
12091
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
12092
+ const SCROLL_BOTTOM_TOLERANCE = 1;
12090
12093
  /**
12091
12094
  * Watches headings in the viewport and toggles `active` on the
12092
12095
  * corresponding TOC links so the reader always knows where they are.
@@ -12113,7 +12116,9 @@ function useScrollHighlight(navRef) {
12113
12116
  if (headings.length === 0)
12114
12117
  return undefined;
12115
12118
  let activeId = null;
12119
+ let clickLocked = false;
12116
12120
  const visible = new Set();
12121
+ const isAtBottom = () => window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
12117
12122
  const activate = (id) => {
12118
12123
  if (id === activeId)
12119
12124
  return;
@@ -12133,6 +12138,17 @@ function useScrollHighlight(navRef) {
12133
12138
  }
12134
12139
  }
12135
12140
  };
12141
+ const updateActive = () => {
12142
+ if (clickLocked)
12143
+ return;
12144
+ if (isAtBottom()) {
12145
+ activate(headings[headings.length - 1].id);
12146
+ return;
12147
+ }
12148
+ const topmost = headings.find(el => visible.has(el.id));
12149
+ if (topmost)
12150
+ activate(topmost.id);
12151
+ };
12136
12152
  const observer = new IntersectionObserver(entries => {
12137
12153
  entries.forEach(e => {
12138
12154
  if (e.isIntersecting)
@@ -12140,14 +12156,42 @@ function useScrollHighlight(navRef) {
12140
12156
  else
12141
12157
  visible.delete(e.target.id);
12142
12158
  });
12143
- // Highlight the topmost visible heading; if none are visible,
12144
- // keep the last active one so the user still has context.
12145
- const topmost = headings.find(el => visible.has(el.id));
12146
- if (topmost)
12147
- activate(topmost.id);
12148
- }, { rootMargin: '0px 0px -60% 0px', threshold: 0 });
12159
+ updateActive();
12160
+ }, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
12161
+ // Also check on scroll so bottom-of-page detection works even when
12162
+ // no headings are crossing the intersection boundary.
12163
+ const onScroll = () => { updateActive(); };
12164
+ // Click a ToC link immediately activate it, suppress the observer
12165
+ // until the smooth scroll finishes, then hand control back.
12166
+ const onClick = (e) => {
12167
+ const anchor = e.target.closest?.('a[href^="#"]');
12168
+ if (!anchor)
12169
+ return;
12170
+ const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
12171
+ if (!linkMap.has(id))
12172
+ return;
12173
+ e.preventDefault();
12174
+ activate(id);
12175
+ clickLocked = true;
12176
+ const unlock = () => { clickLocked = false; };
12177
+ window.addEventListener('scrollend', unlock, { once: true });
12178
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
12179
+ };
12149
12180
  headings.forEach(el => { observer.observe(el); });
12150
- return () => { observer.disconnect(); };
12181
+ window.addEventListener('scroll', onScroll, { passive: true });
12182
+ nav.addEventListener('click', onClick);
12183
+ // Set initial active state for the first heading visible in the viewport
12184
+ const initialHeading = headings.find(el => {
12185
+ const rect = el.getBoundingClientRect();
12186
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
12187
+ });
12188
+ if (initialHeading)
12189
+ activate(initialHeading.id);
12190
+ return () => {
12191
+ observer.disconnect();
12192
+ window.removeEventListener('scroll', onScroll);
12193
+ nav.removeEventListener('click', onClick);
12194
+ };
12151
12195
  }, [navRef, linkCount]);
12152
12196
  }
12153
12197
  function TableOfContents({ children }) {
package/dist/main.node.js CHANGED
@@ -24683,6 +24683,9 @@ function buildLinkMap(nav) {
24683
24683
  });
24684
24684
  return map;
24685
24685
  }
24686
+ const VISIBLE_RATIO = 0.4;
24687
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
24688
+ const SCROLL_BOTTOM_TOLERANCE = 1;
24686
24689
  /**
24687
24690
  * Watches headings in the viewport and toggles `active` on the
24688
24691
  * corresponding TOC links so the reader always knows where they are.
@@ -24709,7 +24712,9 @@ function useScrollHighlight(navRef) {
24709
24712
  if (headings.length === 0)
24710
24713
  return undefined;
24711
24714
  let activeId = null;
24715
+ let clickLocked = false;
24712
24716
  const visible = new Set();
24717
+ const isAtBottom = () => window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
24713
24718
  const activate = (id) => {
24714
24719
  if (id === activeId)
24715
24720
  return;
@@ -24729,6 +24734,17 @@ function useScrollHighlight(navRef) {
24729
24734
  }
24730
24735
  }
24731
24736
  };
24737
+ const updateActive = () => {
24738
+ if (clickLocked)
24739
+ return;
24740
+ if (isAtBottom()) {
24741
+ activate(headings[headings.length - 1].id);
24742
+ return;
24743
+ }
24744
+ const topmost = headings.find(el => visible.has(el.id));
24745
+ if (topmost)
24746
+ activate(topmost.id);
24747
+ };
24732
24748
  const observer = new IntersectionObserver(entries => {
24733
24749
  entries.forEach(e => {
24734
24750
  if (e.isIntersecting)
@@ -24736,14 +24752,42 @@ function useScrollHighlight(navRef) {
24736
24752
  else
24737
24753
  visible.delete(e.target.id);
24738
24754
  });
24739
- // Highlight the topmost visible heading; if none are visible,
24740
- // keep the last active one so the user still has context.
24741
- const topmost = headings.find(el => visible.has(el.id));
24742
- if (topmost)
24743
- activate(topmost.id);
24744
- }, { rootMargin: '0px 0px -60% 0px', threshold: 0 });
24755
+ updateActive();
24756
+ }, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
24757
+ // Also check on scroll so bottom-of-page detection works even when
24758
+ // no headings are crossing the intersection boundary.
24759
+ const onScroll = () => { updateActive(); };
24760
+ // Click a ToC link immediately activate it, suppress the observer
24761
+ // until the smooth scroll finishes, then hand control back.
24762
+ const onClick = (e) => {
24763
+ const anchor = e.target.closest?.('a[href^="#"]');
24764
+ if (!anchor)
24765
+ return;
24766
+ const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
24767
+ if (!linkMap.has(id))
24768
+ return;
24769
+ e.preventDefault();
24770
+ activate(id);
24771
+ clickLocked = true;
24772
+ const unlock = () => { clickLocked = false; };
24773
+ window.addEventListener('scrollend', unlock, { once: true });
24774
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
24775
+ };
24745
24776
  headings.forEach(el => { observer.observe(el); });
24746
- return () => { observer.disconnect(); };
24777
+ window.addEventListener('scroll', onScroll, { passive: true });
24778
+ nav.addEventListener('click', onClick);
24779
+ // Set initial active state for the first heading visible in the viewport
24780
+ const initialHeading = headings.find(el => {
24781
+ const rect = el.getBoundingClientRect();
24782
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
24783
+ });
24784
+ if (initialHeading)
24785
+ activate(initialHeading.id);
24786
+ return () => {
24787
+ observer.disconnect();
24788
+ window.removeEventListener('scroll', onScroll);
24789
+ nav.removeEventListener('click', onClick);
24790
+ };
24747
24791
  }, [navRef, linkCount]);
24748
24792
  }
24749
24793
  function TableOfContents({ children }) {