@reidelsaltres/pureper 0.1.90 → 0.1.92
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/out/foundation/worker/Router.d.ts +5 -0
- package/out/foundation/worker/Router.d.ts.map +1 -1
- package/out/foundation/worker/Router.js +124 -38
- package/out/foundation/worker/Router.js.map +1 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +25 -5
- package/out/index.js.map +1 -1
- package/package.json +1 -1
- package/src/foundation/worker/Router.ts +157 -44
- package/src/index.ts +30 -5
|
@@ -20,5 +20,10 @@ export declare abstract class Router {
|
|
|
20
20
|
static tryFindRoute(url: URL): Route;
|
|
21
21
|
static registerRoute<T extends UniHtml>(path: string, route: string, pageFactory: (search?: URLSearchParams) => T, inheritedRoute?: Route): Promise<Route>;
|
|
22
22
|
private static createPage;
|
|
23
|
+
private static normalizeIncomingUrl;
|
|
24
|
+
private static normalizeRoutePath;
|
|
25
|
+
private static extractRelativePath;
|
|
26
|
+
private static resolveRouteHref;
|
|
27
|
+
private static buildHostedUrl;
|
|
23
28
|
}
|
|
24
29
|
//# sourceMappingURL=Router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Router.d.ts","sourceRoot":"","sources":["../../../src/foundation/worker/Router.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"Router.d.ts","sourceRoot":"","sources":["../../../src/foundation/worker/Router.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,6BAA6B,CAAC;AAKlD,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IAEb,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,KAAK,CAAC,CAAC;CAC9C;AACD,oBAAY,UAAU;IACpB,OAAO,IAAA;IACP,MAAM,IAAA;IACN,IAAI,IAAA;CACL;AAED,eAAO,MAAM,MAAM,EAAE,KAAK,EAAO,CAAC;AAClC,eAAO,MAAM,QAAQ,EAAE,MAAM,EAAO,CAAC;AAErC,8BAAsB,MAAM;WACZ,kBAAkB,CAAC,GAAG,EAAE,GAAG;WAO3B,iBAAiB,IAAI,GAAG,GAAG,IAAI;WAS/B,mBAAmB;WAQnB,aAAa,CAAC,KAAK,EAAE,MAAM;WAO3B,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,GAAE,OAAc;WAmB9C,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,KAAK;WASvB,aAAa,CAAC,CAAC,SAAS,OAAO,EACjD,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,KAAK,CAAC,EAC5C,cAAc,CAAC,EAAE,KAAK,GACrB,OAAO,CAAC,KAAK,CAAC;IAejB,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAQnC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAWjC,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAkBlC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAa/B,OAAO,CAAC,MAAM,CAAC,cAAc;CAY9B"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { HOSTING, HOSTING_ORIGIN } from "../../index.js";
|
|
2
|
+
const HOSTING_BASE = HOSTING.endsWith("/") ? HOSTING.slice(0, -1) : HOSTING;
|
|
3
|
+
const ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
2
4
|
export var AccessType;
|
|
3
5
|
(function (AccessType) {
|
|
4
6
|
AccessType[AccessType["OFFLINE"] = 0] = "OFFLINE";
|
|
@@ -35,76 +37,160 @@ export class Router {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
static legacyRouteTo(route) {
|
|
38
|
-
|
|
39
|
-
if (window.location.
|
|
40
|
-
window.location.replace(
|
|
40
|
+
const target = Router.resolveRouteHref(route);
|
|
41
|
+
if (window.location.href !== target.href) {
|
|
42
|
+
window.location.replace(target.href);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
static tryRouteTo(url, pushState = true) {
|
|
44
|
-
const urlH = new URL(url.href, HOSTING_ORIGIN);
|
|
45
46
|
try {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
page.
|
|
47
|
+
const normalizedUrl = Router.normalizeIncomingUrl(url);
|
|
48
|
+
const found = Router.tryFindRoute(normalizedUrl);
|
|
49
|
+
const page = Router.createPage(found, normalizedUrl.searchParams);
|
|
50
|
+
page.load(document.getElementById("page"));
|
|
51
|
+
const hostedUrl = Router.buildHostedUrl(normalizedUrl);
|
|
49
52
|
if (pushState && typeof window !== "undefined" && window.location) {
|
|
50
|
-
window.history.pushState(page,
|
|
53
|
+
window.history.pushState(page, "", hostedUrl.href);
|
|
51
54
|
}
|
|
55
|
+
window.dispatchEvent(new CustomEvent("spa:navigated", { detail: { url: hostedUrl } }));
|
|
52
56
|
}
|
|
53
57
|
catch (error) {
|
|
54
|
-
console.error("[Router]: Unable to route to ",
|
|
58
|
+
console.error("[Router]: Unable to route to ", url.href, error);
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
static tryFindRoute(url) {
|
|
58
|
-
const
|
|
59
|
-
const found = ROUTES.find(r => r.route ===
|
|
62
|
+
const relativePath = Router.normalizeRoutePath(Router.extractRelativePath(url.pathname));
|
|
63
|
+
const found = ROUTES.find((r) => r.route === relativePath);
|
|
60
64
|
if (!found) {
|
|
61
65
|
throw new Error(`[Router]: Route not found: ${url.pathname}`);
|
|
62
66
|
}
|
|
63
67
|
return found;
|
|
64
68
|
}
|
|
65
69
|
static async registerRoute(path, route, pageFactory, inheritedRoute) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const normalizedRouteSegment = Router.normalizeRoutePath(route);
|
|
71
|
+
const compositeRoute = inheritedRoute
|
|
72
|
+
? Router.normalizeRoutePath(`${inheritedRoute.route}${normalizedRouteSegment}`)
|
|
73
|
+
: normalizedRouteSegment;
|
|
74
|
+
const routeObj = { route: compositeRoute, path, pageFactory };
|
|
70
75
|
ROUTES.push(routeObj);
|
|
71
|
-
|
|
76
|
+
const hostedPreview = Router.buildHostedUrl(new URL(compositeRoute, window.location.origin));
|
|
77
|
+
console.log(`[Router]: Registered route: ${hostedPreview.pathname} -> ${path}`);
|
|
72
78
|
return Promise.resolve(routeObj);
|
|
73
79
|
}
|
|
74
80
|
static createPage(route, search) {
|
|
75
81
|
return route.pageFactory(search);
|
|
76
82
|
}
|
|
83
|
+
static normalizeIncomingUrl(url) {
|
|
84
|
+
if (url.origin && url.origin !== window.location.origin) {
|
|
85
|
+
return url;
|
|
86
|
+
}
|
|
87
|
+
return new URL(url.href, window.location.origin);
|
|
88
|
+
}
|
|
89
|
+
static normalizeRoutePath(route) {
|
|
90
|
+
if (!route || route === "/") {
|
|
91
|
+
return "/";
|
|
92
|
+
}
|
|
93
|
+
const withSlash = route.startsWith("/") ? route : `/${route}`;
|
|
94
|
+
const collapsed = withSlash.replace(/\/{2,}/g, "/");
|
|
95
|
+
const withoutTrailing = collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
96
|
+
return withoutTrailing === "" ? "/" : withoutTrailing;
|
|
97
|
+
}
|
|
98
|
+
static extractRelativePath(pathname) {
|
|
99
|
+
if (!pathname) {
|
|
100
|
+
return "/";
|
|
101
|
+
}
|
|
102
|
+
const ensuredPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
103
|
+
if (HOSTING_BASE && ensuredPath.startsWith(HOSTING_BASE)) {
|
|
104
|
+
const trimmed = ensuredPath.slice(HOSTING_BASE.length);
|
|
105
|
+
if (!trimmed || trimmed === "") {
|
|
106
|
+
return "/";
|
|
107
|
+
}
|
|
108
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
109
|
+
}
|
|
110
|
+
return ensuredPath;
|
|
111
|
+
}
|
|
112
|
+
static resolveRouteHref(route) {
|
|
113
|
+
if (!route || route === "/") {
|
|
114
|
+
return new URL("./", HOSTING_ORIGIN);
|
|
115
|
+
}
|
|
116
|
+
if (ABSOLUTE_URL_PATTERN.test(route)) {
|
|
117
|
+
return new URL(route);
|
|
118
|
+
}
|
|
119
|
+
const normalized = route.startsWith("/") ? `.${route}` : route;
|
|
120
|
+
return new URL(normalized, HOSTING_ORIGIN);
|
|
121
|
+
}
|
|
122
|
+
static buildHostedUrl(url) {
|
|
123
|
+
if (url.origin && url.origin !== window.location.origin) {
|
|
124
|
+
return url;
|
|
125
|
+
}
|
|
126
|
+
const relativePath = Router.normalizeRoutePath(Router.extractRelativePath(url.pathname));
|
|
127
|
+
const routeSegment = relativePath === "/" ? "./" : `.${relativePath}`;
|
|
128
|
+
const hosted = new URL(routeSegment, HOSTING_ORIGIN);
|
|
129
|
+
hosted.search = url.search;
|
|
130
|
+
hosted.hash = url.hash;
|
|
131
|
+
return hosted;
|
|
132
|
+
}
|
|
77
133
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
134
|
+
const resolveLocalNavigationUrl = (rawHref) => {
|
|
135
|
+
if (!rawHref) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const trimmed = rawHref.trim();
|
|
139
|
+
if (trimmed === "" || trimmed.toLowerCase().startsWith("javascript:")) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const candidate = ABSOLUTE_URL_PATTERN.test(trimmed)
|
|
144
|
+
? new URL(trimmed)
|
|
145
|
+
: new URL(trimmed, window.location.href);
|
|
146
|
+
if (candidate.origin !== window.location.origin) {
|
|
147
|
+
return null;
|
|
88
148
|
}
|
|
149
|
+
return candidate;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
89
153
|
}
|
|
154
|
+
};
|
|
155
|
+
// For SPA navigation
|
|
156
|
+
document.addEventListener("click", (e) => {
|
|
157
|
+
const target = e.target;
|
|
158
|
+
if (!target) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const link = target.closest("a[data-link]") ?? target.closest("re-button[data-link]");
|
|
162
|
+
if (!link) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const resolved = resolveLocalNavigationUrl(link.getAttribute("href"));
|
|
166
|
+
if (!resolved) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
Router.tryRouteTo(resolved);
|
|
90
171
|
});
|
|
91
|
-
//For back/forward navigation
|
|
92
|
-
window.addEventListener(
|
|
172
|
+
// For back/forward navigation
|
|
173
|
+
window.addEventListener("popstate", () => {
|
|
93
174
|
try {
|
|
94
|
-
const url = new URL(window.location.href
|
|
175
|
+
const url = new URL(window.location.href);
|
|
95
176
|
Router.tryRouteTo(url, false);
|
|
96
177
|
}
|
|
97
178
|
catch (error) {
|
|
98
|
-
console.error(
|
|
179
|
+
console.error("[Router] (popstate): failed to route to current location", error);
|
|
99
180
|
}
|
|
100
181
|
});
|
|
101
|
-
window.addEventListener(
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
108
|
-
checkRoutes();
|
|
182
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
183
|
+
const routes = ROUTES;
|
|
184
|
+
if (routes.length > 0) {
|
|
185
|
+
const hostedRoutes = routes.map((r) => describeHostedPath(r.route));
|
|
186
|
+
console.log("[Init] [Router]: available routes =", hostedRoutes.join(", "));
|
|
187
|
+
}
|
|
109
188
|
});
|
|
189
|
+
const describeHostedPath = (route) => {
|
|
190
|
+
if (!route || route === "/") {
|
|
191
|
+
return new URL("./", HOSTING_ORIGIN).pathname;
|
|
192
|
+
}
|
|
193
|
+
const segment = route.startsWith("/") ? `.${route}` : route;
|
|
194
|
+
return new URL(segment, HOSTING_ORIGIN).pathname;
|
|
195
|
+
};
|
|
110
196
|
//# sourceMappingURL=Router.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Router.js","sourceRoot":"","sources":["../../../src/foundation/worker/Router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"Router.js","sourceRoot":"","sources":["../../../src/foundation/worker/Router.ts"],"names":[],"mappings":"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"}
|
package/out/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAE3D,cAAc,2CAA2C,CAAC;AAE1D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,WAAW,EAAG,MAAM,EAAC,MAAM,kCAAkC,CAAC;AAEvE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAEhF,cAAc,uBAAuB,CAAC;AAEtC,eAAO,MAAM,OAAO,EAAE,MAA6B,CAAC;AAEpD,eAAO,MAAM,cAAc,EAAE,MAA8C,CAAC"}
|
package/out/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
const computeHostingPath = () => {
|
|
2
|
+
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
3
|
+
return "/";
|
|
4
|
+
}
|
|
5
|
+
const pathname = window.location.pathname || "/";
|
|
6
|
+
if (pathname === "") {
|
|
7
|
+
return "/";
|
|
8
|
+
}
|
|
9
|
+
if (pathname.endsWith("/")) {
|
|
10
|
+
return pathname;
|
|
11
|
+
}
|
|
12
|
+
const lastSlash = pathname.lastIndexOf("/");
|
|
13
|
+
if (lastSlash <= 0) {
|
|
14
|
+
// either no slash at all or only the leading slash
|
|
15
|
+
return pathname.includes(".") ? "/" : `${pathname}/`;
|
|
16
|
+
}
|
|
17
|
+
const lastSegment = pathname.slice(lastSlash + 1);
|
|
18
|
+
if (lastSegment && !lastSegment.includes(".")) {
|
|
19
|
+
// we are on a nested route without trailing slash — treat it as a directory
|
|
20
|
+
return `${pathname}/`;
|
|
21
|
+
}
|
|
22
|
+
return pathname.slice(0, lastSlash + 1);
|
|
23
|
+
};
|
|
1
24
|
export { default as Lazy } from './foundation/api/Lazy.js';
|
|
2
25
|
export * from './foundation/component_api/mixin/Proto.js';
|
|
3
26
|
export { default as UniHtml } from './foundation/component_api/UniHtml.js';
|
|
@@ -9,9 +32,6 @@ export { default as Fetcher } from './foundation/Fetcher.js';
|
|
|
9
32
|
export { Router } from './foundation/worker/Router.js';
|
|
10
33
|
export { default as ServiceWorker } from './foundation/worker/ServiceWorker.js';
|
|
11
34
|
export * from './foundation/Theme.js';
|
|
12
|
-
|
|
13
|
-
export const
|
|
14
|
-
? window.location.href.substring(window.location.origin.length)
|
|
15
|
-
: "";
|
|
16
|
-
export const HOSTING_ORIGIN = window.location.origin + HOSTING;
|
|
35
|
+
export const HOSTING = computeHostingPath();
|
|
36
|
+
export const HOSTING_ORIGIN = `${window.location.origin}${HOSTING}`;
|
|
17
37
|
//# sourceMappingURL=index.js.map
|
package/out/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,GAAW,EAAE;IACvC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC7E,OAAO,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC;IACjD,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACpB,mDAAmD;QACnD,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IACtD,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAClD,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,4EAA4E;QAC5E,OAAO,GAAG,QAAQ,GAAG,CAAC;IACvB,CAAC;IAED,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC;AAIF,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAE3D,cAAc,2CAA2C,CAAC;AAE1D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,WAAW,EAAG,MAAM,EAAC,MAAM,kCAAkC,CAAC;AAEvE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAEhF,cAAc,uBAAuB,CAAC;AAEtC,MAAM,CAAC,MAAM,OAAO,GAAW,kBAAkB,EAAE,CAAC;AAEpD,MAAM,CAAC,MAAM,cAAc,GAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { HOSTING, HOSTING_ORIGIN } from "../../index.js";
|
|
2
2
|
import UniHtml from "../component_api/UniHtml.js";
|
|
3
3
|
|
|
4
|
+
const HOSTING_BASE = HOSTING.endsWith("/") ? HOSTING.slice(0, -1) : HOSTING;
|
|
5
|
+
const ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
6
|
+
|
|
4
7
|
export interface Route<T extends UniHtml = UniHtml> {
|
|
5
8
|
route: string;
|
|
6
9
|
path: string;
|
|
@@ -41,48 +44,57 @@ export abstract class Router {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
|
|
45
47
|
public static legacyRouteTo(route: string) {
|
|
46
|
-
|
|
47
|
-
if (window.location.
|
|
48
|
-
window.location.replace(
|
|
48
|
+
const target = Router.resolveRouteHref(route);
|
|
49
|
+
if (window.location.href !== target.href) {
|
|
50
|
+
window.location.replace(target.href);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
53
|
+
|
|
51
54
|
public static tryRouteTo(url: URL, pushState: boolean = true) {
|
|
52
|
-
const urlH = new URL(url.href, HOSTING_ORIGIN);
|
|
53
55
|
try {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
+
const normalizedUrl = Router.normalizeIncomingUrl(url);
|
|
57
|
+
const found: Route = Router.tryFindRoute(normalizedUrl);
|
|
58
|
+
const page: UniHtml = Router.createPage(found, normalizedUrl.searchParams);
|
|
59
|
+
|
|
60
|
+
page.load(document.getElementById("page")!);
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
const hostedUrl = Router.buildHostedUrl(normalizedUrl);
|
|
58
63
|
if (pushState && typeof window !== "undefined" && window.location) {
|
|
59
|
-
window.history.pushState(page,
|
|
64
|
+
window.history.pushState(page, "", hostedUrl.href);
|
|
60
65
|
}
|
|
66
|
+
|
|
67
|
+
window.dispatchEvent(new CustomEvent("spa:navigated", { detail: { url: hostedUrl } }));
|
|
61
68
|
} catch (error) {
|
|
62
|
-
console.error("[Router]: Unable to route to ",
|
|
69
|
+
console.error("[Router]: Unable to route to ", url.href, error);
|
|
63
70
|
}
|
|
64
71
|
}
|
|
72
|
+
|
|
65
73
|
public static tryFindRoute(url: URL): Route {
|
|
66
|
-
const
|
|
67
|
-
const found = ROUTES.find(r => r.route ===
|
|
74
|
+
const relativePath = Router.normalizeRoutePath(Router.extractRelativePath(url.pathname));
|
|
75
|
+
const found = ROUTES.find((r) => r.route === relativePath);
|
|
68
76
|
if (!found) {
|
|
69
77
|
throw new Error(`[Router]: Route not found: ${url.pathname}`);
|
|
70
78
|
}
|
|
71
79
|
return found;
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
public static async registerRoute<T extends UniHtml>(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
public static async registerRoute<T extends UniHtml>(
|
|
83
|
+
path: string,
|
|
84
|
+
route: string,
|
|
85
|
+
pageFactory: (search?: URLSearchParams) => T,
|
|
86
|
+
inheritedRoute?: Route
|
|
87
|
+
): Promise<Route> {
|
|
88
|
+
const normalizedRouteSegment = Router.normalizeRoutePath(route);
|
|
89
|
+
const compositeRoute = inheritedRoute
|
|
90
|
+
? Router.normalizeRoutePath(`${inheritedRoute.route}${normalizedRouteSegment}`)
|
|
91
|
+
: normalizedRouteSegment;
|
|
79
92
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
let routeObj: Route = { route: tt, path, pageFactory };
|
|
93
|
+
const routeObj: Route = { route: compositeRoute, path, pageFactory };
|
|
83
94
|
|
|
84
95
|
ROUTES.push(routeObj);
|
|
85
|
-
|
|
96
|
+
const hostedPreview = Router.buildHostedUrl(new URL(compositeRoute, window.location.origin));
|
|
97
|
+
console.log(`[Router]: Registered route: ${hostedPreview.pathname} -> ${path}`);
|
|
86
98
|
|
|
87
99
|
return Promise.resolve(routeObj);
|
|
88
100
|
}
|
|
@@ -90,39 +102,140 @@ export abstract class Router {
|
|
|
90
102
|
private static createPage(route: Route, search?: URLSearchParams): UniHtml {
|
|
91
103
|
return route.pageFactory(search);
|
|
92
104
|
}
|
|
105
|
+
|
|
106
|
+
private static normalizeIncomingUrl(url: URL): URL {
|
|
107
|
+
if (url.origin && url.origin !== window.location.origin) {
|
|
108
|
+
return url;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return new URL(url.href, window.location.origin);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private static normalizeRoutePath(route: string): string {
|
|
115
|
+
if (!route || route === "/") {
|
|
116
|
+
return "/";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const withSlash = route.startsWith("/") ? route : `/${route}`;
|
|
120
|
+
const collapsed = withSlash.replace(/\/{2,}/g, "/");
|
|
121
|
+
const withoutTrailing = collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
122
|
+
return withoutTrailing === "" ? "/" : withoutTrailing;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static extractRelativePath(pathname: string): string {
|
|
126
|
+
if (!pathname) {
|
|
127
|
+
return "/";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ensuredPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
131
|
+
|
|
132
|
+
if (HOSTING_BASE && ensuredPath.startsWith(HOSTING_BASE)) {
|
|
133
|
+
const trimmed = ensuredPath.slice(HOSTING_BASE.length);
|
|
134
|
+
if (!trimmed || trimmed === "") {
|
|
135
|
+
return "/";
|
|
136
|
+
}
|
|
137
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return ensuredPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private static resolveRouteHref(route: string): URL {
|
|
144
|
+
if (!route || route === "/") {
|
|
145
|
+
return new URL("./", HOSTING_ORIGIN);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (ABSOLUTE_URL_PATTERN.test(route)) {
|
|
149
|
+
return new URL(route);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const normalized = route.startsWith("/") ? `.${route}` : route;
|
|
153
|
+
return new URL(normalized, HOSTING_ORIGIN);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private static buildHostedUrl(url: URL): URL {
|
|
157
|
+
if (url.origin && url.origin !== window.location.origin) {
|
|
158
|
+
return url;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const relativePath = Router.normalizeRoutePath(Router.extractRelativePath(url.pathname));
|
|
162
|
+
const routeSegment = relativePath === "/" ? "./" : `.${relativePath}`;
|
|
163
|
+
const hosted = new URL(routeSegment, HOSTING_ORIGIN);
|
|
164
|
+
hosted.search = url.search;
|
|
165
|
+
hosted.hash = url.hash;
|
|
166
|
+
return hosted;
|
|
167
|
+
}
|
|
93
168
|
}
|
|
94
169
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
170
|
+
const resolveLocalNavigationUrl = (rawHref: string | null): URL | null => {
|
|
171
|
+
if (!rawHref) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const trimmed = rawHref.trim();
|
|
176
|
+
if (trimmed === "" || trimmed.toLowerCase().startsWith("javascript:")) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const candidate = ABSOLUTE_URL_PATTERN.test(trimmed)
|
|
182
|
+
? new URL(trimmed)
|
|
183
|
+
: new URL(trimmed, window.location.href);
|
|
184
|
+
|
|
185
|
+
if (candidate.origin !== window.location.origin) {
|
|
186
|
+
return null;
|
|
105
187
|
}
|
|
188
|
+
|
|
189
|
+
return candidate;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// For SPA navigation
|
|
196
|
+
document.addEventListener("click", (e) => {
|
|
197
|
+
const target = e.target as Element | null;
|
|
198
|
+
if (!target) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const link = target.closest("a[data-link]") ?? target.closest("re-button[data-link]");
|
|
203
|
+
if (!link) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const resolved = resolveLocalNavigationUrl(link.getAttribute("href"));
|
|
208
|
+
if (!resolved) {
|
|
209
|
+
return;
|
|
106
210
|
}
|
|
211
|
+
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
Router.tryRouteTo(resolved);
|
|
107
214
|
});
|
|
108
215
|
|
|
109
|
-
//For back/forward navigation
|
|
110
|
-
window.addEventListener(
|
|
216
|
+
// For back/forward navigation
|
|
217
|
+
window.addEventListener("popstate", () => {
|
|
111
218
|
try {
|
|
112
|
-
const url = new URL(window.location.href
|
|
219
|
+
const url = new URL(window.location.href);
|
|
113
220
|
Router.tryRouteTo(url, false);
|
|
114
221
|
} catch (error) {
|
|
115
|
-
console.error(
|
|
222
|
+
console.error("[Router] (popstate): failed to route to current location", error);
|
|
116
223
|
}
|
|
117
224
|
});
|
|
118
225
|
|
|
119
|
-
window.addEventListener(
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
226
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
227
|
+
const routes = ROUTES;
|
|
228
|
+
if (routes.length > 0) {
|
|
229
|
+
const hostedRoutes = routes.map((r) => describeHostedPath(r.route));
|
|
230
|
+
console.log("[Init] [Router]: available routes =", hostedRoutes.join(", "));
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const describeHostedPath = (route: string): string => {
|
|
235
|
+
if (!route || route === "/") {
|
|
236
|
+
return new URL("./", HOSTING_ORIGIN).pathname;
|
|
237
|
+
}
|
|
126
238
|
|
|
127
|
-
|
|
128
|
-
|
|
239
|
+
const segment = route.startsWith("/") ? `.${route}` : route;
|
|
240
|
+
return new URL(segment, HOSTING_ORIGIN).pathname;
|
|
241
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
const computeHostingPath = (): string => {
|
|
2
|
+
if (typeof window === "undefined" || typeof window.location === "undefined") {
|
|
3
|
+
return "/";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const pathname = window.location.pathname || "/";
|
|
7
|
+
if (pathname === "") {
|
|
8
|
+
return "/";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (pathname.endsWith("/")) {
|
|
12
|
+
return pathname;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const lastSlash = pathname.lastIndexOf("/");
|
|
16
|
+
if (lastSlash <= 0) {
|
|
17
|
+
// either no slash at all or only the leading slash
|
|
18
|
+
return pathname.includes(".") ? "/" : `${pathname}/`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const lastSegment = pathname.slice(lastSlash + 1);
|
|
22
|
+
if (lastSegment && !lastSegment.includes(".")) {
|
|
23
|
+
// we are on a nested route without trailing slash — treat it as a directory
|
|
24
|
+
return `${pathname}/`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return pathname.slice(0, lastSlash + 1);
|
|
28
|
+
};
|
|
1
29
|
// Public package entry — re-export foundation APIs
|
|
2
30
|
export { default as IElementHolder } from './foundation/api/ElementHolder.js';
|
|
3
31
|
export { default as EmptyConstructor } from './foundation/api/EmptyConstructor.js';
|
|
@@ -19,9 +47,6 @@ export { default as ServiceWorker } from './foundation/worker/ServiceWorker.js';
|
|
|
19
47
|
|
|
20
48
|
export * from './foundation/Theme.js';
|
|
21
49
|
|
|
22
|
-
|
|
23
|
-
export const HOSTING: string = window.location.href.startsWith(window.location.origin)
|
|
24
|
-
? window.location.href.substring(window.location.origin.length)
|
|
25
|
-
: "";
|
|
50
|
+
export const HOSTING: string = computeHostingPath();
|
|
26
51
|
|
|
27
|
-
export const HOSTING_ORIGIN: string = window.location.origin
|
|
52
|
+
export const HOSTING_ORIGIN: string = `${window.location.origin}${HOSTING}`;
|