forked from fireship-io/flamethrower
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandlers.ts
111 lines (97 loc) · 2.78 KB
/
handlers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { RouteChangeData } from './interfaces';
/**
* @param {string} type
* @param {string} id
* scroll to position on next page
*/
export function scrollTo(type: string, id?: string): void {
if (['link', 'go'].includes(type)) {
if (id) {
const el = document.querySelector(id);
el ? el.scrollIntoView({ behavior: 'smooth', block: 'start' }) : window.scrollTo({ top: 0 });
} else {
window.scrollTo({ top: 0 });
}
}
}
/**
* @param {string} url?
* standard formatting for urls
* url == https://example.com/foo/bar
*/
export function fullURL(url?: string): string {
const href = new URL(url || window.location.href).href;
return href.endsWith('/') || href.includes('.') || href.includes('#') ? href : `${href}/`;
}
/**
* @param {string} url
* Writes URL to browser history
*/
export function addToPushState(url: string): void {
if (!window.history.state || window.history.state.url !== url) {
window.history.pushState({ url }, 'internalLink', url);
}
}
// Smooth scroll to anchor link
export function scrollToAnchor(anchor) {
document
.querySelector(anchor)
.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
/**
* @param {PopStateEvent} e
* @returns RouteChangeData
* Handles back button/forward
*/
export function handlePopState(_: PopStateEvent): RouteChangeData {
const next = fullURL();
// addToPushState(next);
return { type: 'popstate', next };
}
/**
* @param {MouseEvent} e
* @returns RouteChangeData
* Organizes link clicks into types
*/
export function handleLinkClick(e: MouseEvent): RouteChangeData {
let anchor: HTMLAnchorElement;
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return { type: 'disqualified' };
}
// Find element containing href
for (let n = e.target as HTMLElement; n.parentNode; n = n.parentNode as HTMLElement) {
if (n.nodeName === 'A') {
anchor = n as HTMLAnchorElement;
break;
}
}
// External links
if (anchor && anchor.host !== location.host) {
anchor.target = '_blank';
return { type: 'external' };
}
// User opt-out
if (anchor && 'cold' in anchor?.dataset) {
return { type: 'disqualified' };
}
// Link qualified
if (anchor?.hasAttribute('href')) {
const ahref = anchor.getAttribute('href');
const url = new URL(ahref, location.href);
// Start router takeover
e.preventDefault();
// If anchor, scroll,
if (ahref?.startsWith('#')) {
scrollToAnchor(ahref);
return { type: 'scrolled' };
}
// ID to scroll to after navigation, like /route/#some-id
const scrollId = ahref.match(/#([\w'-]+)\b/g)?.[0];
const next = fullURL(url.href);
const prev = fullURL();
// addToPushState(next);
return { type: 'link', next, prev, scrollId };
} else {
return { type: 'noop' };
}
}