-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtru.mjs
More file actions
129 lines (114 loc) · 4.14 KB
/
Copy pathtru.mjs
File metadata and controls
129 lines (114 loc) · 4.14 KB
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* tru.mjs v1 - Tiny Router Utility
* Routers without the pain.
*
* @author Shinon
* @license MIT
*/
/**
* @typedef {Object} TruRouter
* @property {function(string, function(Record<string, string>): void): TruRouter} add Registers a new route.
* @property {function(function(string): void): TruRouter} fallback Registers a fallback for unmatched routes.
* @property {function(string): void} go Programmatically navigates to a new path.
* @property {function(): TruRouter} start Initializes event listeners and triggers the initial route.
*/
/**
* Creates a new instance of the Tiny Router Utility.
*
* @returns {TruRouter} The router instance.
*/
export function createRouter() {
/** @type {Array<{pattern: URLPattern, callback: function(Record<string, string>): void}>} */
const routes = [];
/** @type {function(string): void} */
let notFoundHandler = () => console.warn("Route not found");
/**
* Internal method to match a URL against registered patterns.
* @param {string} url - The full URL to match.
*/
const match = (url) => {
for (const route of routes) {
// 1. Let the browser's native API do the heavy lifting
const result = route.pattern.exec(url);
if (result) {
// 2. URLPattern automatically extracts named capture groups!
// e.g., for '/users/:id', it returns { id: "42" }
return route.callback(result.pathname.groups);
}
}
return notFoundHandler(new URL(url).pathname);
};
/** @type {TruRouter} */
const router = {
/**
* Registers a new route.
* @param {string} path - The path pattern (e.g., '/users/:id').
* @param {function(Record<string, string>): void} callback - Function to execute when matched.
* @returns {TruRouter} The router instance for chaining.
*/
add(path, callback) {
// Create a native URLPattern. By passing an object,
// we tell it to strictly match the pathname and ignore the domain.
routes.push({
pattern: new URLPattern({ pathname: path }),
callback,
});
return this;
},
/**
* Registers a fallback handler for unmatched routes.
* @param {function(string): void} callback - Function to execute when no route matches.
* @returns {TruRouter} The router instance for chaining.
*/
fallback(callback) {
notFoundHandler = callback;
return this;
},
/**
* Programmatically navigates to a new path.
* @param {string} path - The local path to navigate to.
*/
go(path) {
history.pushState(null, "", path);
match(window.location.href); // Pass the full, newly updated URL
},
/**
* Initializes the router, attaches event listeners, and triggers the initial route.
* @returns {TruRouter} The router instance.
*/
start() {
window.addEventListener("popstate", () => match(window.location.href));
document.body.addEventListener("click", (e) => {
/** @type {HTMLAnchorElement | null} */
const link = e.target.closest("a");
if (
link && // Check if link is valid
link.href && // And has a href
link.origin === window.location.origin && // Same origin to prevent external navigation
!link.hasAttribute("download") && // Ignore file downloads
link.target !== "_blank" &&
link.target !== "_top" && // Ignore iframe escapes
!link.hasAttribute("data-bypass") && // Custom escape hatch
e.button === 0 &&
!e.metaKey &&
!e.ctrlKey
) {
// Check if ANY registered pattern matches this URL
// Note: URLPattern.test() is faster than .exec() just for checking true/false
const isKnownRoute = routes.some((route) =>
route.pattern.test(link.href),
);
// If we know the route, hijack the click.
// If we don't know it, do nothing and let the browser handle the error/navigation natively.
if (isKnownRoute) {
e.preventDefault();
router.go(link.href);
}
}
});
match(window.location.href);
return this;
},
};
return router;
}