@docmd/ui 0.4.10 → 0.4.11
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.
- package/assets/css/docmd-main.css +2 -1
- package/assets/js/docmd-main.js +40 -14
- package/package.json +1 -1
package/assets/js/docmd-main.js
CHANGED
|
@@ -151,9 +151,32 @@
|
|
|
151
151
|
if (document.body.dataset.spaEnabled !== 'true') return;
|
|
152
152
|
|
|
153
153
|
let currentPath = window.location.pathname;
|
|
154
|
+
const pageCache = new Map();
|
|
155
|
+
let prefetchTimer = null;
|
|
156
|
+
|
|
157
|
+
// Intent-based Hover Prefetching
|
|
158
|
+
document.addEventListener('mouseover', (e) => {
|
|
159
|
+
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
160
|
+
if (!link || link.target === '_blank' || link.hasAttribute('download')) return;
|
|
161
|
+
|
|
162
|
+
const url = new URL(link.href).href;
|
|
163
|
+
if (new URL(url).origin !== location.origin) return;
|
|
164
|
+
if (pageCache.has(url)) return;
|
|
165
|
+
|
|
166
|
+
// Wait 65ms to ensure the user actually intends to click
|
|
167
|
+
clearTimeout(prefetchTimer);
|
|
168
|
+
prefetchTimer = setTimeout(() => {
|
|
169
|
+
pageCache.set(url, fetch(url).then(res => {
|
|
170
|
+
if (!res.ok) throw new Error('Prefetch failed');
|
|
171
|
+
return { html: res.text(), finalUrl: res.url };
|
|
172
|
+
}).catch(() => pageCache.delete(url)));
|
|
173
|
+
}, 65);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Cancel prefetch if the mouse leaves before the 65ms "intent" delay
|
|
177
|
+
document.addEventListener('mouseout', () => clearTimeout(prefetchTimer));
|
|
154
178
|
|
|
155
179
|
document.addEventListener('click', async (e) => {
|
|
156
|
-
// Ignore clicks on expand/collapse arrows so they don't trigger navigation
|
|
157
180
|
if (e.target.closest('.collapse-icon-wrapper')) return;
|
|
158
181
|
|
|
159
182
|
const link = e.target.closest('.sidebar-nav a, .page-navigation a');
|
|
@@ -168,7 +191,7 @@
|
|
|
168
191
|
});
|
|
169
192
|
|
|
170
193
|
window.addEventListener('popstate', () => {
|
|
171
|
-
if (window.location.pathname === currentPath) return;
|
|
194
|
+
if (window.location.pathname === currentPath) return;
|
|
172
195
|
navigateTo(window.location.href, false);
|
|
173
196
|
});
|
|
174
197
|
|
|
@@ -176,13 +199,22 @@
|
|
|
176
199
|
const layout = document.querySelector('.content-layout');
|
|
177
200
|
|
|
178
201
|
try {
|
|
179
|
-
// Lock height to prevent scrollbar jitter/dragging during DOM swap
|
|
180
202
|
if (layout) layout.style.minHeight = layout.getBoundingClientRect().height + 'px';
|
|
181
203
|
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
let data;
|
|
205
|
+
if (pageCache.has(url)) {
|
|
206
|
+
data = await pageCache.get(url);
|
|
207
|
+
data.html = await data.html;
|
|
208
|
+
} else {
|
|
209
|
+
const res = await fetch(url);
|
|
210
|
+
if (!res.ok) throw new Error('Fetch failed');
|
|
211
|
+
data = { html: await res.text(), finalUrl: res.url };
|
|
212
|
+
pageCache.set(url, Promise.resolve(data));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const finalUrl = data.finalUrl;
|
|
216
|
+
const html = data.html;
|
|
217
|
+
|
|
186
218
|
const parser = new DOMParser();
|
|
187
219
|
const doc = parser.parseFromString(html, 'text/html');
|
|
188
220
|
|
|
@@ -205,24 +237,21 @@
|
|
|
205
237
|
}
|
|
206
238
|
});
|
|
207
239
|
|
|
208
|
-
//
|
|
240
|
+
// Sync Sidebar State
|
|
209
241
|
const oldLis = Array.from(document.querySelectorAll('.sidebar-nav li'));
|
|
210
242
|
const newLis = Array.from(doc.querySelectorAll('.sidebar-nav li'));
|
|
211
243
|
|
|
212
244
|
oldLis.forEach((oldLi, i) => {
|
|
213
245
|
const newLi = newLis[i];
|
|
214
246
|
if (newLi) {
|
|
215
|
-
// Sync active classes
|
|
216
247
|
oldLi.classList.toggle('active', newLi.classList.contains('active'));
|
|
217
248
|
oldLi.classList.toggle('active-parent', newLi.classList.contains('active-parent'));
|
|
218
249
|
|
|
219
|
-
// Add expanded class if the new page requires it, but NEVER remove it
|
|
220
250
|
if (newLi.classList.contains('expanded')) {
|
|
221
251
|
oldLi.classList.add('expanded');
|
|
222
252
|
oldLi.setAttribute('aria-expanded', 'true');
|
|
223
253
|
}
|
|
224
254
|
|
|
225
|
-
// Sync relative hrefs
|
|
226
255
|
const oldA = oldLi.querySelector('a');
|
|
227
256
|
const newA = newLi.querySelector('a');
|
|
228
257
|
if (oldA && newA) {
|
|
@@ -232,7 +261,6 @@
|
|
|
232
261
|
}
|
|
233
262
|
});
|
|
234
263
|
|
|
235
|
-
// 3. Swap Body Components (Removed .sidebar-nav from this list)
|
|
236
264
|
const selectorsToSwap =[
|
|
237
265
|
'.content-layout',
|
|
238
266
|
'.page-header .header-title',
|
|
@@ -247,7 +275,6 @@
|
|
|
247
275
|
if (oldEl && newEl) oldEl.innerHTML = newEl.innerHTML;
|
|
248
276
|
});
|
|
249
277
|
|
|
250
|
-
// Scroll & Init
|
|
251
278
|
const hash = new URL(finalUrl).hash;
|
|
252
279
|
if (hash) {
|
|
253
280
|
document.querySelector(hash)?.scrollIntoView();
|
|
@@ -262,7 +289,6 @@
|
|
|
262
289
|
|
|
263
290
|
document.dispatchEvent(new CustomEvent('docmd:page-mounted', { detail: { url: finalUrl } }));
|
|
264
291
|
|
|
265
|
-
// Unlock height smoothly
|
|
266
292
|
setTimeout(() => {
|
|
267
293
|
const newLayout = document.querySelector('.content-layout');
|
|
268
294
|
if (newLayout) newLayout.style.minHeight = '';
|