sourcey 3.4.1 → 3.4.4

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.
@@ -4,192 +4,204 @@
4
4
  // on the matching #nav .nav-link. All visual styling is in sourcey.css;
5
5
  // this file only manages the class.
6
6
  (function () {
7
- var navbar = document.getElementById('navbar');
8
- var navLinks = document.querySelectorAll('#nav .nav-link');
9
- var targets = document.querySelectorAll('[data-traverse-target]');
10
-
11
- // For prose pages, also track heading elements that TOC links point to
12
- var tocHeadingEls = [];
13
- document.querySelectorAll('#toc .toc-item').forEach(function (link) {
14
- var href = link.getAttribute('href');
15
- if (href && href.indexOf('#') !== -1) {
16
- var el = document.getElementById(href.split('#')[1]);
17
- if (el) tocHeadingEls.push(el);
18
- }
19
- });
20
-
21
- if (!targets.length && !tocHeadingEls.length) return;
22
- if (!navLinks.length && !tocHeadingEls.length) return;
7
+ function init() {
8
+ var navbar = document.getElementById('navbar');
9
+ var navLinks = document.querySelectorAll('#nav .nav-link');
10
+ var targets = document.querySelectorAll('[data-traverse-target]');
11
+
12
+ // For prose pages, also track heading elements that TOC links point to
13
+ var tocHeadingEls = [];
14
+ document.querySelectorAll('#toc .toc-item').forEach(function (link) {
15
+ var href = link.getAttribute('href');
16
+ if (href && href.indexOf('#') !== -1) {
17
+ var el = document.getElementById(href.split('#')[1]);
18
+ if (el) tocHeadingEls.push(el);
19
+ }
20
+ });
23
21
 
24
- var currentId = null;
25
- var clickedId = null; // When set, overrides scroll-based activation
26
- var clickTimer = null;
22
+ if (!targets.length && !tocHeadingEls.length) return;
23
+ if (!navLinks.length && !tocHeadingEls.length) return;
27
24
 
28
- // Map traverse-target id → nav link element (by matching href fragment)
29
- var linkMap = {};
30
- navLinks.forEach(function (link) {
31
- var href = link.getAttribute('href');
32
- if (href && href.indexOf('#') !== -1) {
33
- linkMap[href.split('#')[1]] = link;
34
- }
35
- });
36
-
37
- // If no fragment-based nav links AND no TOC headings, nothing to track
38
- if (!Object.keys(linkMap).length && !tocHeadingEls.length) return;
39
-
40
- // TOC links (right sidebar) — share the same scroll tracking
41
- var tocLinks = document.querySelectorAll('#toc .toc-item');
42
- var tocMap = {};
43
- tocLinks.forEach(function (link) {
44
- var href = link.getAttribute('href');
45
- if (href && href.indexOf('#') !== -1) {
46
- tocMap[href.split('#')[1]] = link;
47
- }
48
- });
25
+ var currentId = null;
26
+ var clickedId = null; // When set, overrides scroll-based activation
27
+ var clickTimer = null;
49
28
 
50
- // Only manage active state on fragment-linked nav items and TOC items.
51
- // Doc page nav links keep their SSR-set active class untouched.
52
- var fragmentNavLinks = Object.values(linkMap);
29
+ // Map traverse-target id nav link element (by matching href fragment)
30
+ var linkMap = {};
31
+ navLinks.forEach(function (link) {
32
+ var href = link.getAttribute('href');
33
+ if (href && href.indexOf('#') !== -1) {
34
+ linkMap[href.split('#')[1]] = link;
35
+ }
36
+ });
53
37
 
54
- function activate(id, force) {
55
- if (id === currentId && !force) return;
56
- currentId = id;
38
+ // If no fragment-based nav links AND no TOC headings, nothing to track
39
+ if (!Object.keys(linkMap).length && !tocHeadingEls.length) return;
57
40
 
58
- fragmentNavLinks.forEach(function (link) {
59
- link.classList.remove('active');
60
- });
41
+ // TOC links (right sidebar) — share the same scroll tracking
42
+ var tocLinks = document.querySelectorAll('#toc .toc-item');
43
+ var tocMap = {};
61
44
  tocLinks.forEach(function (link) {
62
- link.classList.remove('active');
45
+ var href = link.getAttribute('href');
46
+ if (href && href.indexOf('#') !== -1) {
47
+ tocMap[href.split('#')[1]] = link;
48
+ }
63
49
  });
64
50
 
65
- var active = linkMap[id];
66
- if (active) {
67
- active.classList.add('active');
68
- active.scrollIntoView({ block: 'nearest', behavior: 'auto' });
51
+ // Only manage active state on fragment-linked nav items and TOC items.
52
+ // Doc page nav links keep their SSR-set active class untouched.
53
+ var fragmentNavLinks = Object.values(linkMap);
54
+
55
+ function activate(id, force) {
56
+ if (id === currentId && !force) return;
57
+ currentId = id;
58
+
59
+ fragmentNavLinks.forEach(function (link) {
60
+ link.classList.remove('active');
61
+ });
62
+ tocLinks.forEach(function (link) {
63
+ link.classList.remove('active');
64
+ });
65
+
66
+ var active = linkMap[id];
67
+ if (active) {
68
+ active.classList.add('active');
69
+ active.scrollIntoView({ block: 'nearest', behavior: 'auto' });
70
+ }
71
+
72
+ var tocActive = tocMap[id];
73
+ if (tocActive) {
74
+ tocActive.classList.add('active');
75
+ }
69
76
  }
70
77
 
71
- var tocActive = tocMap[id];
72
- if (tocActive) {
73
- tocActive.classList.add('active');
78
+ // Scroll to element with header offset.
79
+ // For the first traverse target, scroll to the very top of the page
80
+ // so the title and all top padding are visible.
81
+ function scrollToId(id, behavior) {
82
+ var el = document.getElementById(id);
83
+ if (!el) return;
84
+ var firstTarget = targets.length ? targets[0].getAttribute('data-traverse-target') : null;
85
+ if (id === firstTarget) {
86
+ window.scrollTo({ top: 0, behavior: behavior });
87
+ return;
88
+ }
89
+ var offset = (navbar ? navbar.offsetHeight : 0) - 1;
90
+ window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - offset, behavior: behavior });
74
91
  }
75
- }
76
92
 
77
- // Scroll to element with header offset.
78
- // For the first traverse target, scroll to the very top of the page
79
- // so the title and all top padding are visible.
80
- function scrollToId(id, behavior) {
81
- var el = document.getElementById(id);
82
- if (!el) return;
83
- var firstTarget = targets.length ? targets[0].getAttribute('data-traverse-target') : null;
84
- if (id === firstTarget) {
85
- window.scrollTo({ top: 0, behavior: behavior });
86
- return;
93
+ // Handle anchor clicks: activate highlight, scroll with header offset, lock scroll tracker.
94
+ function handleAnchorClick(e, selector) {
95
+ var link = e.target.closest(selector);
96
+ if (!link) return;
97
+ var href = link.getAttribute('href');
98
+ if (!href || href.indexOf('#') === -1) return;
99
+ var id = href.split('#')[1];
100
+ if (!id) return;
101
+
102
+ e.preventDefault();
103
+ clickedId = id;
104
+ activate(id, true);
105
+ scrollToId(id, 'smooth');
106
+ history.replaceState(null, '', '#' + id);
107
+
108
+ clearTimeout(clickTimer);
109
+ clickTimer = setTimeout(function () { clickedId = null; }, 800);
87
110
  }
88
- var offset = (navbar ? navbar.offsetHeight : 0) - 1;
89
- window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - offset, behavior: behavior });
90
- }
91
111
 
92
- // Handle anchor clicks: activate highlight, scroll with header offset, lock scroll tracker.
93
- function handleAnchorClick(e, selector) {
94
- var link = e.target.closest(selector);
95
- if (!link) return;
96
- var href = link.getAttribute('href');
97
- if (!href || href.indexOf('#') === -1) return;
98
- var id = href.split('#')[1];
99
- if (!id) return;
100
-
101
- e.preventDefault();
102
- clickedId = id;
103
- activate(id, true);
104
- scrollToId(id, 'smooth');
105
- history.replaceState(null, '', '#' + id);
106
-
107
- clearTimeout(clickTimer);
108
- clickTimer = setTimeout(function () { clickedId = null; }, 800);
109
- }
112
+ var tocEl = document.getElementById('toc');
113
+ if (tocEl) tocEl.addEventListener('click', function (e) { handleAnchorClick(e, '.toc-item'); });
110
114
 
111
- var tocEl = document.getElementById('toc');
112
- if (tocEl) tocEl.addEventListener('click', function (e) { handleAnchorClick(e, '.toc-item'); });
115
+ var navEl = document.getElementById('nav');
116
+ if (navEl) navEl.addEventListener('click', function (e) { handleAnchorClick(e, '.nav-link'); });
113
117
 
114
- var navEl = document.getElementById('nav');
115
- if (navEl) navEl.addEventListener('click', function (e) { handleAnchorClick(e, '.nav-link'); });
118
+ // Use scroll event for reliable activation at all positions.
119
+ function onScroll() {
120
+ // If a nav link was just clicked, don't override it until scroll settles
121
+ if (clickedId) return;
116
122
 
117
- // Use scroll event for reliable activation at all positions.
118
- function onScroll() {
119
- // If a nav link was just clicked, don't override it until scroll settles
120
- if (clickedId) return;
123
+ var threshold = (navbar ? navbar.offsetHeight : 0) + 20;
124
+ var best = null;
121
125
 
122
- var threshold = (navbar ? navbar.offsetHeight : 0) + 20;
123
- var best = null;
126
+ // Check API traverse targets
127
+ for (var i = 0; i < targets.length; i++) {
128
+ var top = targets[i].getBoundingClientRect().top;
129
+ if (top <= threshold) {
130
+ best = targets[i].getAttribute('data-traverse-target');
131
+ } else {
132
+ break;
133
+ }
134
+ }
124
135
 
125
- // Check API traverse targets
126
- for (var i = 0; i < targets.length; i++) {
127
- var top = targets[i].getBoundingClientRect().top;
128
- if (top <= threshold) {
129
- best = targets[i].getAttribute('data-traverse-target');
130
- } else {
131
- break;
136
+ // Check TOC heading elements (prose pages)
137
+ for (var j = 0; j < tocHeadingEls.length; j++) {
138
+ var hTop = tocHeadingEls[j].getBoundingClientRect().top;
139
+ if (hTop <= threshold) {
140
+ best = tocHeadingEls[j].id;
141
+ } else {
142
+ break;
143
+ }
132
144
  }
133
- }
134
145
 
135
- // Check TOC heading elements (prose pages)
136
- for (var j = 0; j < tocHeadingEls.length; j++) {
137
- var hTop = tocHeadingEls[j].getBoundingClientRect().top;
138
- if (hTop <= threshold) {
139
- best = tocHeadingEls[j].id;
140
- } else {
141
- break;
146
+ // At top of page, default to first section
147
+ var allTargets = targets.length ? targets : tocHeadingEls;
148
+ if (!best && allTargets.length) {
149
+ best = targets.length
150
+ ? allTargets[0].getAttribute('data-traverse-target')
151
+ : allTargets[0].id;
152
+ }
153
+
154
+ // At bottom of page, force last section active
155
+ if (window.innerHeight + window.scrollY >= document.body.scrollHeight - 10) {
156
+ var last = allTargets[allTargets.length - 1];
157
+ best = targets.length ? last.getAttribute('data-traverse-target') : last.id;
142
158
  }
143
- }
144
159
 
145
- // At top of page, default to first section
146
- var allTargets = targets.length ? targets : tocHeadingEls;
147
- if (!best && allTargets.length) {
148
- best = targets.length
149
- ? allTargets[0].getAttribute('data-traverse-target')
150
- : allTargets[0].id;
160
+ if (best) activate(best);
151
161
  }
152
162
 
153
- // At bottom of page, force last section active
154
- if (window.innerHeight + window.scrollY >= document.body.scrollHeight - 10) {
155
- var last = allTargets[allTargets.length - 1];
156
- best = targets.length ? last.getAttribute('data-traverse-target') : last.id;
163
+ // Throttle scroll handler
164
+ var ticking = false;
165
+ window.addEventListener('scroll', function () {
166
+ if (!ticking) {
167
+ ticking = true;
168
+ requestAnimationFrame(function () {
169
+ onScroll();
170
+ ticking = false;
171
+ });
172
+ }
173
+ }, { passive: true });
174
+
175
+ // Activate on initial load based on URL hash or scroll position.
176
+ // Always use JS scroll to override the browser's native hash scroll,
177
+ // which uses CSS scroll-margin-top and may not match the actual navbar height.
178
+ var hash = window.location.hash.slice(1)
179
+ || new URLSearchParams(window.location.search).get('target');
180
+ if (hash && document.getElementById(hash)) {
181
+ activate(hash);
182
+ // Defer to next frame so browser's native hash scroll completes first
183
+ requestAnimationFrame(function () { scrollToId(hash, 'instant'); });
184
+ } else {
185
+ onScroll();
157
186
  }
158
187
 
159
- if (best) activate(best);
188
+ // Handle hash changes (back/forward navigation)
189
+ window.addEventListener('hashchange', function () {
190
+ var id = window.location.hash.slice(1);
191
+ if (id) {
192
+ activate(id, true);
193
+ scrollToId(id, 'smooth');
194
+ }
195
+ });
160
196
  }
161
197
 
162
- // Throttle scroll handler
163
- var ticking = false;
164
- window.addEventListener('scroll', function () {
165
- if (!ticking) {
166
- ticking = true;
167
- requestAnimationFrame(function () {
168
- onScroll();
169
- ticking = false;
170
- });
171
- }
172
- }, { passive: true });
173
-
174
- // Activate on initial load based on URL hash or scroll position.
175
- // Always use JS scroll to override the browser's native hash scroll,
176
- // which uses CSS scroll-margin-top and may not match the actual navbar height.
177
- var hash = window.location.hash.slice(1)
198
+ var target = window.location.hash.slice(1)
178
199
  || new URLSearchParams(window.location.search).get('target');
179
- if (hash && document.getElementById(hash)) {
180
- activate(hash);
181
- // Defer to next frame so browser's native hash scroll completes first
182
- requestAnimationFrame(function () { scrollToId(hash, 'instant'); });
200
+ if (target) {
201
+ requestAnimationFrame(init);
202
+ } else if ('requestIdleCallback' in window) {
203
+ window.requestIdleCallback(init, { timeout: 250 });
183
204
  } else {
184
- onScroll();
205
+ window.addEventListener('load', init, { once: true });
185
206
  }
186
-
187
- // Handle hash changes (back/forward navigation)
188
- window.addEventListener('hashchange', function () {
189
- var id = window.location.hash.slice(1);
190
- if (id) {
191
- activate(id, true);
192
- scrollToId(id, 'smooth');
193
- }
194
- });
195
207
  })();