@drawnagency/primitives 0.1.110 → 0.1.111
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.
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface NavProgressOptions {
|
|
2
|
+
showDelayMs?: number;
|
|
3
|
+
safetyMs?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wire the bar rendered by Layout.astro. No-ops when the element is absent,
|
|
7
|
+
* which is how /edit opts out — Astro hoists a layout's module script to the
|
|
8
|
+
* page bundle regardless of the branch it was rendered in, so the element, not
|
|
9
|
+
* the script tag, is the gate.
|
|
10
|
+
*
|
|
11
|
+
* Returns a teardown function (used by tests; production never unwires).
|
|
12
|
+
*/
|
|
13
|
+
export declare function initNavProgress(options?: NavProgressOptions): () => void;
|
|
14
|
+
//# sourceMappingURL=nav-progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-progress.d.ts","sourceRoot":"","sources":["../../src/lib/nav-progress.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0BD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAoDxE"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Top progress bar for viewer page navigation. Viewer pages are per-request SSR
|
|
2
|
+
// renders, so a click can sit for a beat with no feedback beyond the browser's
|
|
3
|
+
// own tab spinner; this gives that wait an in-page affordance in the brand
|
|
4
|
+
// color.
|
|
5
|
+
//
|
|
6
|
+
// There is no client router here — the browser performs a real navigation and
|
|
7
|
+
// keeps painting the outgoing page until the new response commits, which is
|
|
8
|
+
// exactly the window the bar fills. Nothing hides it: the incoming document
|
|
9
|
+
// replaces it. The only teardown paths are a bfcache restore (pageshow) and the
|
|
10
|
+
// safety timeout for a navigation that never lands.
|
|
11
|
+
|
|
12
|
+
/** Delay before the bar appears, so a prefetched (near-instant) navigation
|
|
13
|
+
* never flashes it. Mirrors EditorLoading's reasoning. */
|
|
14
|
+
const SHOW_DELAY_MS = 200;
|
|
15
|
+
/** Give up if the navigation never commits (blocked, aborted, offline) rather
|
|
16
|
+
* than leaving a stuck bar across the top of a page the viewer is still on. */
|
|
17
|
+
const SAFETY_MS = 15_000;
|
|
18
|
+
|
|
19
|
+
export interface NavProgressOptions {
|
|
20
|
+
showDelayMs?: number;
|
|
21
|
+
safetyMs?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isModifiedClick(event: MouseEvent): boolean {
|
|
25
|
+
// Anything that doesn't navigate this tab: middle/right click, new tab, new
|
|
26
|
+
// window, download.
|
|
27
|
+
return event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Would this click actually navigate the current tab to a different document? */
|
|
31
|
+
function navigatesAway(link: HTMLAnchorElement): boolean {
|
|
32
|
+
if (link.hasAttribute("download") || link.dataset.navProgress === "false") return false;
|
|
33
|
+
if (link.target && link.target !== "_self") return false;
|
|
34
|
+
if (!link.getAttribute("href")) return false;
|
|
35
|
+
|
|
36
|
+
let url: URL;
|
|
37
|
+
try {
|
|
38
|
+
url = new URL(link.href, window.location.href);
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// mailto:/tel: land here too — their origin is never the page's.
|
|
43
|
+
if (url.origin !== window.location.origin) return false;
|
|
44
|
+
// Same-document hash links, including the nav's `#doc=` document launchers.
|
|
45
|
+
return url.pathname !== window.location.pathname || url.search !== window.location.search;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wire the bar rendered by Layout.astro. No-ops when the element is absent,
|
|
50
|
+
* which is how /edit opts out — Astro hoists a layout's module script to the
|
|
51
|
+
* page bundle regardless of the branch it was rendered in, so the element, not
|
|
52
|
+
* the script tag, is the gate.
|
|
53
|
+
*
|
|
54
|
+
* Returns a teardown function (used by tests; production never unwires).
|
|
55
|
+
*/
|
|
56
|
+
export function initNavProgress(options?: NavProgressOptions): () => void {
|
|
57
|
+
const bar = document.querySelector<HTMLElement>("[data-nav-progress]");
|
|
58
|
+
if (!bar) return () => {};
|
|
59
|
+
|
|
60
|
+
const showDelayMs = options?.showDelayMs ?? SHOW_DELAY_MS;
|
|
61
|
+
const safetyMs = options?.safetyMs ?? SAFETY_MS;
|
|
62
|
+
|
|
63
|
+
let showTimer: ReturnType<typeof setTimeout> | null = null;
|
|
64
|
+
let safetyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
65
|
+
|
|
66
|
+
// Arrow consts, not function declarations: hoisting would discard the
|
|
67
|
+
// null-narrowing `bar` just got from the guard above.
|
|
68
|
+
const reset = () => {
|
|
69
|
+
if (showTimer) clearTimeout(showTimer);
|
|
70
|
+
if (safetyTimer) clearTimeout(safetyTimer);
|
|
71
|
+
showTimer = null;
|
|
72
|
+
safetyTimer = null;
|
|
73
|
+
bar.classList.remove("is-active");
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const start = () => {
|
|
77
|
+
reset();
|
|
78
|
+
showTimer = setTimeout(() => {
|
|
79
|
+
showTimer = null;
|
|
80
|
+
bar.classList.add("is-active");
|
|
81
|
+
}, showDelayMs);
|
|
82
|
+
safetyTimer = setTimeout(reset, safetyMs);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const onClick = (event: MouseEvent) => {
|
|
86
|
+
if (event.defaultPrevented || isModifiedClick(event)) return;
|
|
87
|
+
const target = event.target;
|
|
88
|
+
const link = target instanceof Element ? target.closest("a") : null;
|
|
89
|
+
if (!(link instanceof HTMLAnchorElement)) return;
|
|
90
|
+
if (!navigatesAway(link)) return;
|
|
91
|
+
start();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// A bfcache restore replays the outgoing page as it was — bar included, since
|
|
95
|
+
// it was mid-animation when the viewer left. Clear it.
|
|
96
|
+
const onPageShow = (event: PageTransitionEvent) => {
|
|
97
|
+
if (event.persisted) reset();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
document.addEventListener("click", onClick);
|
|
101
|
+
window.addEventListener("pageshow", onPageShow);
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
reset();
|
|
105
|
+
document.removeEventListener("click", onClick);
|
|
106
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
107
|
+
};
|
|
108
|
+
}
|