fynixui 1.0.10
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/LICENSE +21 -0
- package/dist/context/context.d.ts +19 -0
- package/dist/context/context.d.ts.map +1 -0
- package/dist/context/context.js +4 -0
- package/dist/context/context.js.map +7 -0
- package/dist/custom/button.d.ts +2 -0
- package/dist/custom/button.d.ts.map +1 -0
- package/dist/custom/button.js +4 -0
- package/dist/custom/button.js.map +7 -0
- package/dist/custom/index.d.ts +3 -0
- package/dist/custom/index.d.ts.map +1 -0
- package/dist/custom/index.js +2 -0
- package/dist/custom/index.js.map +7 -0
- package/dist/custom/path.d.ts +14 -0
- package/dist/custom/path.d.ts.map +1 -0
- package/dist/custom/path.js +20 -0
- package/dist/custom/path.js.map +7 -0
- package/dist/error/errorOverlay.d.ts +3 -0
- package/dist/error/errorOverlay.d.ts.map +1 -0
- package/dist/error/errorOverlay.js +84 -0
- package/dist/error/errorOverlay.js.map +7 -0
- package/dist/fynix/index.d.ts +6 -0
- package/dist/fynix/index.d.ts.map +1 -0
- package/dist/fynix/index.js +5 -0
- package/dist/fynix/index.js.map +7 -0
- package/dist/hooks/nixAsync.d.ts +20 -0
- package/dist/hooks/nixAsync.d.ts.map +1 -0
- package/dist/hooks/nixAsync.js +114 -0
- package/dist/hooks/nixAsync.js.map +7 -0
- package/dist/hooks/nixAsyncCache.d.ts +19 -0
- package/dist/hooks/nixAsyncCache.d.ts.map +1 -0
- package/dist/hooks/nixAsyncCache.js +137 -0
- package/dist/hooks/nixAsyncCache.js.map +7 -0
- package/dist/hooks/nixAsyncDebounce.d.ts +22 -0
- package/dist/hooks/nixAsyncDebounce.d.ts.map +1 -0
- package/dist/hooks/nixAsyncDebounce.js +77 -0
- package/dist/hooks/nixAsyncDebounce.js.map +7 -0
- package/dist/hooks/nixAsyncQuery.d.ts +16 -0
- package/dist/hooks/nixAsyncQuery.d.ts.map +1 -0
- package/dist/hooks/nixAsyncQuery.js +87 -0
- package/dist/hooks/nixAsyncQuery.js.map +7 -0
- package/dist/hooks/nixCallback.d.ts +2 -0
- package/dist/hooks/nixCallback.d.ts.map +1 -0
- package/dist/hooks/nixCallback.js +34 -0
- package/dist/hooks/nixCallback.js.map +7 -0
- package/dist/hooks/nixComputed.d.ts +16 -0
- package/dist/hooks/nixComputed.d.ts.map +1 -0
- package/dist/hooks/nixComputed.js +175 -0
- package/dist/hooks/nixComputed.js.map +7 -0
- package/dist/hooks/nixDebounce.d.ts +11 -0
- package/dist/hooks/nixDebounce.d.ts.map +1 -0
- package/dist/hooks/nixDebounce.js +55 -0
- package/dist/hooks/nixDebounce.js.map +7 -0
- package/dist/hooks/nixEffect.d.ts +4 -0
- package/dist/hooks/nixEffect.d.ts.map +1 -0
- package/dist/hooks/nixEffect.js +75 -0
- package/dist/hooks/nixEffect.js.map +7 -0
- package/dist/hooks/nixFor.d.ts +13 -0
- package/dist/hooks/nixFor.d.ts.map +1 -0
- package/dist/hooks/nixFor.js +43 -0
- package/dist/hooks/nixFor.js.map +7 -0
- package/dist/hooks/nixForm.d.ts +33 -0
- package/dist/hooks/nixForm.d.ts.map +1 -0
- package/dist/hooks/nixForm.js +123 -0
- package/dist/hooks/nixForm.js.map +7 -0
- package/dist/hooks/nixFormAsync.d.ts +42 -0
- package/dist/hooks/nixFormAsync.d.ts.map +1 -0
- package/dist/hooks/nixFormAsync.js +169 -0
- package/dist/hooks/nixFormAsync.js.map +7 -0
- package/dist/hooks/nixInterval.d.ts +2 -0
- package/dist/hooks/nixInterval.d.ts.map +1 -0
- package/dist/hooks/nixInterval.js +23 -0
- package/dist/hooks/nixInterval.js.map +7 -0
- package/dist/hooks/nixLazy.d.ts +8 -0
- package/dist/hooks/nixLazy.d.ts.map +1 -0
- package/dist/hooks/nixLazy.js +58 -0
- package/dist/hooks/nixLazy.js.map +7 -0
- package/dist/hooks/nixLazyAsync.d.ts +10 -0
- package/dist/hooks/nixLazyAsync.d.ts.map +1 -0
- package/dist/hooks/nixLazyAsync.js +71 -0
- package/dist/hooks/nixLazyAsync.js.map +7 -0
- package/dist/hooks/nixLazyFormAsync.d.ts +50 -0
- package/dist/hooks/nixLazyFormAsync.d.ts.map +1 -0
- package/dist/hooks/nixLazyFormAsync.js +221 -0
- package/dist/hooks/nixLazyFormAsync.js.map +7 -0
- package/dist/hooks/nixLocalStorage.d.ts +8 -0
- package/dist/hooks/nixLocalStorage.d.ts.map +1 -0
- package/dist/hooks/nixLocalStorage.js +136 -0
- package/dist/hooks/nixLocalStorage.js.map +7 -0
- package/dist/hooks/nixMemo.d.ts +2 -0
- package/dist/hooks/nixMemo.d.ts.map +1 -0
- package/dist/hooks/nixMemo.js +30 -0
- package/dist/hooks/nixMemo.js.map +7 -0
- package/dist/hooks/nixPrevious.d.ts +2 -0
- package/dist/hooks/nixPrevious.d.ts.map +1 -0
- package/dist/hooks/nixPrevious.js +15 -0
- package/dist/hooks/nixPrevious.js.map +7 -0
- package/dist/hooks/nixRef.d.ts +4 -0
- package/dist/hooks/nixRef.d.ts.map +1 -0
- package/dist/hooks/nixRef.js +17 -0
- package/dist/hooks/nixRef.js.map +7 -0
- package/dist/hooks/nixState.d.ts +15 -0
- package/dist/hooks/nixState.d.ts.map +1 -0
- package/dist/hooks/nixState.js +127 -0
- package/dist/hooks/nixState.js.map +7 -0
- package/dist/hooks/nixStore.d.ts +10 -0
- package/dist/hooks/nixStore.d.ts.map +1 -0
- package/dist/hooks/nixStore.js +103 -0
- package/dist/hooks/nixStore.js.map +7 -0
- package/dist/package.json +221 -0
- package/dist/plugins/vite-plugin-res.d.ts +38 -0
- package/dist/plugins/vite-plugin-res.d.ts.map +1 -0
- package/dist/plugins/vite-plugin-res.js +106 -0
- package/dist/plugins/vite-plugin-res.js.map +7 -0
- package/dist/router/router.d.ts +48 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/router.js +872 -0
- package/dist/router/router.js.map +7 -0
- package/dist/runtime.d.ts +124 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1361 -0
- package/dist/runtime.js.map +7 -0
- package/package.json +254 -0
- package/types/fnx.d.ts +34 -0
- package/types/fynix-ui.d.ts +323 -0
- package/types/global.d.ts +279 -0
- package/types/index.d.ts +37 -0
- package/types/jsx.d.ts +993 -0
- package/types/vite-env.d.ts +545 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
import { mount } from "../runtime";
|
|
2
|
+
const MAX_CACHE_SIZE = 50;
|
|
3
|
+
const PROPS_NAMESPACE = "__fynixLinkProps__";
|
|
4
|
+
const MAX_LISTENERS = 100;
|
|
5
|
+
const ALLOWED_PROTOCOLS = ["http:", "https:", ""];
|
|
6
|
+
const RENDER_DEBOUNCE = 10;
|
|
7
|
+
let routerInstance = null;
|
|
8
|
+
let isRouterInitialized = false;
|
|
9
|
+
function isExternal(url) {
|
|
10
|
+
return /^https?:\/\//.test(url);
|
|
11
|
+
}
|
|
12
|
+
function escapeHTML(str) {
|
|
13
|
+
if (typeof str !== "string")
|
|
14
|
+
return "";
|
|
15
|
+
return str
|
|
16
|
+
.replace(/&/g, "&")
|
|
17
|
+
.replace(/</g, "<")
|
|
18
|
+
.replace(/>/g, ">")
|
|
19
|
+
.replace(/"/g, """)
|
|
20
|
+
.replace(/'/g, "'")
|
|
21
|
+
.replace(/`/g, "`")
|
|
22
|
+
.replace(/\//g, "/")
|
|
23
|
+
.replace(/=/g, "=")
|
|
24
|
+
.replace(/\(/g, "(")
|
|
25
|
+
.replace(/\)/g, ")")
|
|
26
|
+
.replace(/\{/g, "{")
|
|
27
|
+
.replace(/\}/g, "}")
|
|
28
|
+
.replace(/\[/g, "[")
|
|
29
|
+
.replace(/\]/g, "]");
|
|
30
|
+
}
|
|
31
|
+
function sanitizeContent(content) {
|
|
32
|
+
return content
|
|
33
|
+
.replace(/<script[^>]*>.*?<\/script>/gis, "")
|
|
34
|
+
.replace(/<iframe[^>]*>.*?<\/iframe>/gis, "")
|
|
35
|
+
.replace(/<object[^>]*>.*?<\/object>/gis, "")
|
|
36
|
+
.replace(/<embed[^>]*>/gi, "")
|
|
37
|
+
.replace(/<link[^>]*>/gi, "")
|
|
38
|
+
.replace(/on\w+\s*=/gi, "")
|
|
39
|
+
.replace(/javascript:/gi, "")
|
|
40
|
+
.replace(/vbscript:/gi, "")
|
|
41
|
+
.replace(/data:/gi, "")
|
|
42
|
+
.replace(/expression\s*\(/gi, "");
|
|
43
|
+
}
|
|
44
|
+
function sanitizeProps(props) {
|
|
45
|
+
const sanitized = {};
|
|
46
|
+
for (const [key, value] of Object.entries(props)) {
|
|
47
|
+
if (typeof key !== "string" ||
|
|
48
|
+
key.startsWith("__") ||
|
|
49
|
+
key.includes("javascript") ||
|
|
50
|
+
key.includes("on")) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === "string") {
|
|
54
|
+
const cleanContent = sanitizeContent(value);
|
|
55
|
+
sanitized[key] = escapeHTML(cleanContent);
|
|
56
|
+
}
|
|
57
|
+
else if (typeof value === "object" && value !== null) {
|
|
58
|
+
if (Object.keys(value).length < 50) {
|
|
59
|
+
sanitized[key] = sanitizeProps(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (typeof value === "number" || typeof value === "boolean") {
|
|
63
|
+
sanitized[key] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sanitized;
|
|
67
|
+
}
|
|
68
|
+
function isValidURL(url) {
|
|
69
|
+
try {
|
|
70
|
+
const suspiciousPatterns = [
|
|
71
|
+
/javascript:/i,
|
|
72
|
+
/vbscript:/i,
|
|
73
|
+
/data:/i,
|
|
74
|
+
/mailto:/i,
|
|
75
|
+
/tel:/i,
|
|
76
|
+
/ftp:/i,
|
|
77
|
+
/file:/i,
|
|
78
|
+
/%2f%2f/i,
|
|
79
|
+
/%5c%5c/i,
|
|
80
|
+
/\\\\/,
|
|
81
|
+
/@/,
|
|
82
|
+
];
|
|
83
|
+
if (suspiciousPatterns.some((pattern) => pattern.test(url))) {
|
|
84
|
+
console.warn("[Router] Security: Suspicious URL pattern blocked");
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const parsed = new URL(url, window.location.origin);
|
|
88
|
+
if (parsed.origin !== window.location.origin) {
|
|
89
|
+
console.warn("[Router] Security: Cross-origin navigation blocked");
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (!ALLOWED_PROTOCOLS.includes(parsed.protocol)) {
|
|
93
|
+
console.warn("[Router] Security: Dangerous protocol blocked:", parsed.protocol);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const decodedPath = decodeURIComponent(parsed.pathname);
|
|
97
|
+
if (decodedPath !== parsed.pathname && /[<>"'`]/.test(decodedPath)) {
|
|
98
|
+
console.warn("[Router] Security: Encoded XSS attempt blocked");
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (url.length > 2048) {
|
|
102
|
+
console.warn("[Router] Security: Excessively long URL blocked");
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.warn("[Router] Security: Invalid URL blocked");
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function sanitizePath(path) {
|
|
113
|
+
if (typeof path !== "string")
|
|
114
|
+
return "/";
|
|
115
|
+
try {
|
|
116
|
+
path = decodeURIComponent(path);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
console.warn("[Router] Invalid URL encoding in path");
|
|
120
|
+
return "/";
|
|
121
|
+
}
|
|
122
|
+
path = path.replace(/\0/g, "");
|
|
123
|
+
path = path.replace(/\\/g, "/");
|
|
124
|
+
path = path.replace(/\/+/g, "/");
|
|
125
|
+
path = path
|
|
126
|
+
.split("/")
|
|
127
|
+
.filter((part) => part !== ".." && part !== ".")
|
|
128
|
+
.join("/");
|
|
129
|
+
if (!path.startsWith("/")) {
|
|
130
|
+
path = "/" + path;
|
|
131
|
+
}
|
|
132
|
+
if (path.length > 1 && path.endsWith("/")) {
|
|
133
|
+
path = path.slice(0, -1);
|
|
134
|
+
}
|
|
135
|
+
return path || "/";
|
|
136
|
+
}
|
|
137
|
+
function tryGlobPaths() {
|
|
138
|
+
try {
|
|
139
|
+
let modules = import.meta.glob("/src/**/*.{fnx,tsx,jsx,ts,js}", {
|
|
140
|
+
eager: true,
|
|
141
|
+
});
|
|
142
|
+
if (Object.keys(modules).length === 0) {
|
|
143
|
+
modules = import.meta.glob(["./**/*.fnx", "./**/*.tsx", "./**/*.jsx", "./**/*.ts", "./**/*.js"], { eager: true });
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(modules).length === 0) {
|
|
146
|
+
modules = import.meta.glob(["../**/*.fnx", "../**/*.tsx", "../**/*.jsx"], { eager: true });
|
|
147
|
+
}
|
|
148
|
+
return modules || {};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error("[Router] Failed to load modules:", error);
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function filePathToRoute(filePath) {
|
|
156
|
+
let route = filePath
|
|
157
|
+
.replace(/^.*\/src/, "")
|
|
158
|
+
.replace(/\.(ts|tsx|js|jsx|fnx)$/, "")
|
|
159
|
+
.replace(/\/view$/, "")
|
|
160
|
+
.replace(/\/$/, "");
|
|
161
|
+
if (!route)
|
|
162
|
+
route = "/";
|
|
163
|
+
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
164
|
+
return route;
|
|
165
|
+
}
|
|
166
|
+
function matchDynamicRoute(path, dynamicRoutes) {
|
|
167
|
+
for (const route of dynamicRoutes) {
|
|
168
|
+
const match = path.match(route.regex);
|
|
169
|
+
if (match) {
|
|
170
|
+
const params = {};
|
|
171
|
+
route.params.forEach((param, i) => {
|
|
172
|
+
const matchValue = match[i + 1];
|
|
173
|
+
params[param] = escapeHTML(matchValue || "");
|
|
174
|
+
});
|
|
175
|
+
return { component: route.component, params };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
function deserializeProps(props) {
|
|
181
|
+
if (!props || typeof props !== "object")
|
|
182
|
+
return {};
|
|
183
|
+
const deserialized = {};
|
|
184
|
+
for (const [key, value] of Object.entries(props)) {
|
|
185
|
+
if (typeof key !== "string" || key.startsWith("__")) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
deserialized[key] = value;
|
|
189
|
+
}
|
|
190
|
+
return deserialized;
|
|
191
|
+
}
|
|
192
|
+
function normalizePath(path) {
|
|
193
|
+
return sanitizePath(path);
|
|
194
|
+
}
|
|
195
|
+
function generateCacheKey() {
|
|
196
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
197
|
+
return crypto.randomUUID();
|
|
198
|
+
}
|
|
199
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}-${Math.random()
|
|
200
|
+
.toString(36)
|
|
201
|
+
.slice(2)}`;
|
|
202
|
+
}
|
|
203
|
+
function addToCache(cache, key, value) {
|
|
204
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
205
|
+
const firstKey = cache.keys().next().value;
|
|
206
|
+
if (typeof firstKey === "string") {
|
|
207
|
+
const evicted = cache.get(firstKey);
|
|
208
|
+
if (evicted && typeof evicted === "object") {
|
|
209
|
+
Object.values(evicted).forEach((val) => {
|
|
210
|
+
if (val && typeof val === "object" && "cleanup" in val) {
|
|
211
|
+
try {
|
|
212
|
+
val.cleanup();
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
cache.delete(firstKey);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
cache.set(key, value);
|
|
223
|
+
}
|
|
224
|
+
const MANAGED_META = [
|
|
225
|
+
{ key: "description", name: "description" },
|
|
226
|
+
{ key: "keywords", name: "keywords" },
|
|
227
|
+
{ key: "twitterCard", name: "twitter:card" },
|
|
228
|
+
{ key: "ogTitle", property: "og:title" },
|
|
229
|
+
{ key: "ogDescription", property: "og:description" },
|
|
230
|
+
{ key: "ogImage", property: "og:image" },
|
|
231
|
+
];
|
|
232
|
+
function updateMetaTags(meta = {}) {
|
|
233
|
+
if (!meta || typeof meta !== "object")
|
|
234
|
+
return;
|
|
235
|
+
if (meta.title && typeof meta.title === "string") {
|
|
236
|
+
const sanitizedTitle = escapeHTML(meta.title).substring(0, 60);
|
|
237
|
+
document.title = sanitizedTitle;
|
|
238
|
+
}
|
|
239
|
+
MANAGED_META.forEach((def) => {
|
|
240
|
+
const value = meta[def.key];
|
|
241
|
+
const selector = def.name
|
|
242
|
+
? `meta[name="${CSS.escape(def.name)}"]`
|
|
243
|
+
: `meta[property="${CSS.escape(def.property || "")}"]`;
|
|
244
|
+
let el = document.querySelector(selector);
|
|
245
|
+
if (value == null) {
|
|
246
|
+
if (el)
|
|
247
|
+
el.remove();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (typeof value !== "string")
|
|
251
|
+
return;
|
|
252
|
+
const cleanValue = sanitizeContent(value);
|
|
253
|
+
const sanitizedValue = escapeHTML(cleanValue).substring(0, 300);
|
|
254
|
+
if (/javascript:|vbscript:|data:|<|>/i.test(sanitizedValue)) {
|
|
255
|
+
console.warn(`[Router] Security: Blocked suspicious meta content for ${def.key}`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!el) {
|
|
259
|
+
el = document.createElement("meta");
|
|
260
|
+
if (def.name)
|
|
261
|
+
el.setAttribute("name", def.name);
|
|
262
|
+
if (def.property)
|
|
263
|
+
el.setAttribute("property", def.property);
|
|
264
|
+
document.head.appendChild(el);
|
|
265
|
+
}
|
|
266
|
+
el.setAttribute("content", sanitizedValue);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
class EnterpriseRouter {
|
|
270
|
+
constructor() {
|
|
271
|
+
this.routeCache = new Map();
|
|
272
|
+
this.preloadQueue = new Set();
|
|
273
|
+
this.routeMatchCache = new Map();
|
|
274
|
+
this.routes = {};
|
|
275
|
+
}
|
|
276
|
+
setRoutes(routes) {
|
|
277
|
+
if (!routes || typeof routes !== "object") {
|
|
278
|
+
console.warn("[EnterpriseRouter] Invalid routes configuration");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
this.routes = routes;
|
|
282
|
+
}
|
|
283
|
+
async preloadRoute(path) {
|
|
284
|
+
if (this.routeCache.has(path))
|
|
285
|
+
return;
|
|
286
|
+
const route = this.routes[path];
|
|
287
|
+
if (route?.component) {
|
|
288
|
+
const loadRoute = async () => {
|
|
289
|
+
try {
|
|
290
|
+
const component = await route.component();
|
|
291
|
+
this.routeCache.set(path, component);
|
|
292
|
+
route.prefetch?.forEach((prefetchPath) => {
|
|
293
|
+
this.preloadQueue.add(prefetchPath);
|
|
294
|
+
});
|
|
295
|
+
console.log(`[EnterpriseRouter] Preloaded route: ${path}`);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.warn(`[EnterpriseRouter] Failed to preload route ${path}:`, error);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
if ("requestIdleCallback" in window) {
|
|
302
|
+
requestIdleCallback(loadRoute);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
setTimeout(loadRoute, 0);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
matchRoute(path) {
|
|
310
|
+
const cached = this.routeMatchCache.get(path);
|
|
311
|
+
if (cached !== undefined)
|
|
312
|
+
return cached;
|
|
313
|
+
const match = this.computeRouteMatch(path);
|
|
314
|
+
if (this.routeMatchCache.size > 100) {
|
|
315
|
+
const firstKey = this.routeMatchCache.keys().next().value;
|
|
316
|
+
if (firstKey !== undefined) {
|
|
317
|
+
this.routeMatchCache.delete(firstKey);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
this.routeMatchCache.set(path, match);
|
|
321
|
+
return match;
|
|
322
|
+
}
|
|
323
|
+
computeRouteMatch(path) {
|
|
324
|
+
const segments = path.split("/").filter(Boolean);
|
|
325
|
+
for (const [routePath, routeConfig] of Object.entries(this.routes)) {
|
|
326
|
+
const routeSegments = routePath.split("/").filter(Boolean);
|
|
327
|
+
if (segments.length !== routeSegments.length)
|
|
328
|
+
continue;
|
|
329
|
+
const params = {};
|
|
330
|
+
let isMatch = true;
|
|
331
|
+
for (let i = 0; i < segments.length; i++) {
|
|
332
|
+
const segment = segments[i];
|
|
333
|
+
const routeSegment = routeSegments[i];
|
|
334
|
+
if (routeSegment && segment) {
|
|
335
|
+
if (routeSegment.startsWith(":")) {
|
|
336
|
+
params[routeSegment.slice(1)] = segment;
|
|
337
|
+
}
|
|
338
|
+
else if (segment !== routeSegment) {
|
|
339
|
+
isMatch = false;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
isMatch = false;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (isMatch) {
|
|
349
|
+
return {
|
|
350
|
+
component: routeConfig.component,
|
|
351
|
+
params,
|
|
352
|
+
meta: typeof routeConfig.meta === "function"
|
|
353
|
+
? routeConfig.meta(params)
|
|
354
|
+
: routeConfig.meta,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
async checkRouteGuard(route, path, params) {
|
|
361
|
+
if (!route.guard)
|
|
362
|
+
return true;
|
|
363
|
+
if (route.guard.canActivate) {
|
|
364
|
+
const canActivate = await route.guard.canActivate(path, params);
|
|
365
|
+
return canActivate;
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
getPreloadedComponent(path) {
|
|
370
|
+
return this.routeCache.get(path);
|
|
371
|
+
}
|
|
372
|
+
clearCache() {
|
|
373
|
+
this.routeCache.clear();
|
|
374
|
+
this.routeMatchCache.clear();
|
|
375
|
+
this.preloadQueue.clear();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
class LayoutRouter {
|
|
379
|
+
constructor() {
|
|
380
|
+
this.layoutCache = new Map();
|
|
381
|
+
this.keepAliveComponents = new Map();
|
|
382
|
+
}
|
|
383
|
+
renderNestedRoutes(routes, segments) {
|
|
384
|
+
if (segments.length === 0)
|
|
385
|
+
return null;
|
|
386
|
+
const [currentSegment, ...remainingSegments] = segments;
|
|
387
|
+
const currentRoute = routes.find((r) => r.path === currentSegment);
|
|
388
|
+
if (!currentRoute)
|
|
389
|
+
return null;
|
|
390
|
+
let content;
|
|
391
|
+
if (remainingSegments.length > 0 && currentRoute.children) {
|
|
392
|
+
content = this.renderNestedRoutes(currentRoute.children, remainingSegments);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
if (currentRoute.keepAlive && currentSegment) {
|
|
396
|
+
content = this.renderKeepAlive(currentRoute.component, currentSegment);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
content = this.renderComponent(currentRoute.component);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (currentRoute.layout) {
|
|
403
|
+
const layoutKey = `${currentSegment}_layout`;
|
|
404
|
+
let layoutComponent = this.layoutCache.get(layoutKey);
|
|
405
|
+
if (!layoutComponent) {
|
|
406
|
+
layoutComponent = this.renderComponent(currentRoute.layout);
|
|
407
|
+
this.layoutCache.set(layoutKey, layoutComponent);
|
|
408
|
+
}
|
|
409
|
+
return this.renderComponent(currentRoute.layout, { children: content });
|
|
410
|
+
}
|
|
411
|
+
return content;
|
|
412
|
+
}
|
|
413
|
+
renderKeepAlive(component, key) {
|
|
414
|
+
if (this.keepAliveComponents.has(key)) {
|
|
415
|
+
return this.keepAliveComponents.get(key);
|
|
416
|
+
}
|
|
417
|
+
const rendered = this.renderComponent(component);
|
|
418
|
+
this.keepAliveComponents.set(key, rendered);
|
|
419
|
+
return rendered;
|
|
420
|
+
}
|
|
421
|
+
renderComponent(component, props = {}) {
|
|
422
|
+
try {
|
|
423
|
+
return component(props);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error("[LayoutRouter] Component render error:", error);
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
cleanup() {
|
|
431
|
+
this.layoutCache.clear();
|
|
432
|
+
this.keepAliveComponents.clear();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const enterpriseRouter = new EnterpriseRouter();
|
|
436
|
+
const layoutRouter = new LayoutRouter();
|
|
437
|
+
function createFynix() {
|
|
438
|
+
const isDevMode = import.meta.hot !== undefined;
|
|
439
|
+
if (routerInstance && isRouterInitialized && !isDevMode) {
|
|
440
|
+
console.warn("[Router] Router already initialized, returning existing instance");
|
|
441
|
+
return routerInstance;
|
|
442
|
+
}
|
|
443
|
+
if (isDevMode && routerInstance) {
|
|
444
|
+
console.log("[Router] HMR: Cleaning up old router instance");
|
|
445
|
+
routerInstance.cleanup();
|
|
446
|
+
routerInstance = null;
|
|
447
|
+
isRouterInitialized = false;
|
|
448
|
+
}
|
|
449
|
+
let rootSelector = "#app-root";
|
|
450
|
+
let currentPath = null;
|
|
451
|
+
let isDestroyed = false;
|
|
452
|
+
let listenerCount = 0;
|
|
453
|
+
let renderTimeout = null;
|
|
454
|
+
let lastNavigationTime = 0;
|
|
455
|
+
const NAVIGATION_RATE_LIMIT = 100;
|
|
456
|
+
const listeners = [];
|
|
457
|
+
if (!window[PROPS_NAMESPACE]) {
|
|
458
|
+
window[PROPS_NAMESPACE] = {};
|
|
459
|
+
}
|
|
460
|
+
if (isDevMode && window.__fynixPropsCache) {
|
|
461
|
+
window.__fynixPropsCache.clear();
|
|
462
|
+
}
|
|
463
|
+
const propsCache = window.__fynixPropsCache || new Map();
|
|
464
|
+
window.__fynixPropsCache = propsCache;
|
|
465
|
+
const modules = tryGlobPaths();
|
|
466
|
+
const routes = {};
|
|
467
|
+
const dynamicRoutes = [];
|
|
468
|
+
for (const [filePath, mod] of Object.entries(modules)) {
|
|
469
|
+
const routePath = filePathToRoute(filePath);
|
|
470
|
+
let component = undefined;
|
|
471
|
+
if (mod && typeof mod === "object") {
|
|
472
|
+
if ("default" in mod && mod.default) {
|
|
473
|
+
component = mod.default;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const keys = Object.keys(mod);
|
|
477
|
+
const firstKey = keys.length > 0 ? keys[0] : undefined;
|
|
478
|
+
if (firstKey !== undefined &&
|
|
479
|
+
typeof firstKey === "string" &&
|
|
480
|
+
typeof mod[firstKey] !== "undefined") {
|
|
481
|
+
component = mod[firstKey];
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const values = Object.values(mod).filter(Boolean);
|
|
485
|
+
if (values.length > 0) {
|
|
486
|
+
component = values[0];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!component || typeof routePath !== "string")
|
|
492
|
+
continue;
|
|
493
|
+
const hasDynamic = /:[^/]+/.test(routePath);
|
|
494
|
+
if (hasDynamic) {
|
|
495
|
+
dynamicRoutes.push({
|
|
496
|
+
pattern: routePath,
|
|
497
|
+
regex: new RegExp("^" + routePath.replace(/:[^/]+/g, "([^/]+)") + "$"),
|
|
498
|
+
component,
|
|
499
|
+
params: [...routePath.matchAll(/:([^/]+)/g)]
|
|
500
|
+
.map((m) => m[1])
|
|
501
|
+
.filter((p) => typeof p === "string"),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
routes[routePath] = component;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function renderRouteImmediate() {
|
|
509
|
+
if (isDestroyed)
|
|
510
|
+
return;
|
|
511
|
+
const path = normalizePath(window.location.pathname);
|
|
512
|
+
let Page = routes[path];
|
|
513
|
+
let params = {};
|
|
514
|
+
let routeProps = {};
|
|
515
|
+
const enterpriseMatch = enterpriseRouter.matchRoute(path);
|
|
516
|
+
if (enterpriseMatch) {
|
|
517
|
+
const preloadedComponent = enterpriseRouter.getPreloadedComponent(path);
|
|
518
|
+
if (preloadedComponent) {
|
|
519
|
+
Page = preloadedComponent;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
Page = enterpriseMatch.component;
|
|
523
|
+
}
|
|
524
|
+
params = enterpriseMatch.params;
|
|
525
|
+
}
|
|
526
|
+
if (!Page) {
|
|
527
|
+
const match = matchDynamicRoute(path, dynamicRoutes);
|
|
528
|
+
if (match) {
|
|
529
|
+
Page = match.component;
|
|
530
|
+
params = match.params;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const root = document.querySelector(rootSelector);
|
|
534
|
+
if (!root) {
|
|
535
|
+
console.error("[Router] Root element not found:", rootSelector);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (!Page) {
|
|
539
|
+
root.innerHTML = "";
|
|
540
|
+
const container = document.createElement("div");
|
|
541
|
+
container.style.cssText =
|
|
542
|
+
"padding: 2rem; text-align: center; font-family: system-ui, sans-serif;";
|
|
543
|
+
const heading = document.createElement("h2");
|
|
544
|
+
heading.textContent = "404 Not Found";
|
|
545
|
+
heading.style.cssText = "color: #dc2626; margin-bottom: 1rem;";
|
|
546
|
+
const pathInfo = document.createElement("p");
|
|
547
|
+
const safePath = escapeHTML(sanitizeContent(path));
|
|
548
|
+
pathInfo.textContent = `Path: ${safePath}`;
|
|
549
|
+
pathInfo.style.cssText = "color: #6b7280; margin-bottom: 2rem;";
|
|
550
|
+
const backButton = document.createElement("button");
|
|
551
|
+
backButton.textContent = "Go Back";
|
|
552
|
+
backButton.style.cssText =
|
|
553
|
+
"padding: 0.5rem 1rem; background: #3b82f6; color: white; border: none; border-radius: 0.25rem; cursor: pointer;";
|
|
554
|
+
backButton.onclick = () => window.history.back();
|
|
555
|
+
container.appendChild(heading);
|
|
556
|
+
container.appendChild(pathInfo);
|
|
557
|
+
container.appendChild(backButton);
|
|
558
|
+
root.appendChild(container);
|
|
559
|
+
updateMetaTags({ title: "404 - Page Not Found" });
|
|
560
|
+
["/", "/home", "/about"].forEach((commonPath) => {
|
|
561
|
+
enterpriseRouter.preloadRoute(commonPath).catch(console.warn);
|
|
562
|
+
});
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const state = (window.history.state || {});
|
|
566
|
+
let passedProps = {};
|
|
567
|
+
if (state.__fynixCacheKey && propsCache.has(state.__fynixCacheKey)) {
|
|
568
|
+
passedProps = propsCache.get(state.__fynixCacheKey);
|
|
569
|
+
}
|
|
570
|
+
else if (state.serializedProps) {
|
|
571
|
+
passedProps = deserializeProps(state.serializedProps);
|
|
572
|
+
}
|
|
573
|
+
if (Page.props) {
|
|
574
|
+
routeProps = typeof Page.props === "function" ? Page.props() : Page.props;
|
|
575
|
+
}
|
|
576
|
+
if (Page.meta) {
|
|
577
|
+
const meta = typeof Page.meta === "function" ? Page.meta(params) : Page.meta;
|
|
578
|
+
updateMetaTags(meta);
|
|
579
|
+
}
|
|
580
|
+
const unsafeProps = {
|
|
581
|
+
...routeProps,
|
|
582
|
+
...passedProps,
|
|
583
|
+
params,
|
|
584
|
+
};
|
|
585
|
+
const safeProps = sanitizeProps(unsafeProps);
|
|
586
|
+
window.__lastRouteProps = safeProps;
|
|
587
|
+
try {
|
|
588
|
+
mount(Page, rootSelector, safeProps);
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
console.error("[Router] Mount failed:", err);
|
|
592
|
+
root.innerHTML = "";
|
|
593
|
+
const errorDiv = document.createElement("pre");
|
|
594
|
+
errorDiv.style.color = "red";
|
|
595
|
+
errorDiv.textContent = "Mount Error occurred";
|
|
596
|
+
root.appendChild(errorDiv);
|
|
597
|
+
}
|
|
598
|
+
currentPath = path;
|
|
599
|
+
}
|
|
600
|
+
function renderRoute() {
|
|
601
|
+
if (isDestroyed)
|
|
602
|
+
return;
|
|
603
|
+
if (renderTimeout) {
|
|
604
|
+
clearTimeout(renderTimeout);
|
|
605
|
+
}
|
|
606
|
+
renderTimeout = setTimeout(async () => {
|
|
607
|
+
await renderRouteImmediate();
|
|
608
|
+
renderTimeout = null;
|
|
609
|
+
}, RENDER_DEBOUNCE);
|
|
610
|
+
}
|
|
611
|
+
function navigate(path, props = {}) {
|
|
612
|
+
if (isDestroyed)
|
|
613
|
+
return;
|
|
614
|
+
const now = Date.now();
|
|
615
|
+
if (now - lastNavigationTime < NAVIGATION_RATE_LIMIT) {
|
|
616
|
+
console.warn("[Router] Security: Navigation rate limited");
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
lastNavigationTime = now;
|
|
620
|
+
const normalizedPath = normalizePath(path);
|
|
621
|
+
if (!isValidURL(window.location.origin + normalizedPath)) {
|
|
622
|
+
console.error("[Router] Invalid navigation URL");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (normalizedPath === currentPath)
|
|
626
|
+
return;
|
|
627
|
+
enterpriseRouter.preloadRoute(normalizedPath).catch(console.warn);
|
|
628
|
+
const sanitizedProps = sanitizeProps(props);
|
|
629
|
+
const cacheKey = generateCacheKey();
|
|
630
|
+
addToCache(propsCache, cacheKey, sanitizedProps);
|
|
631
|
+
try {
|
|
632
|
+
window.history.pushState({ __fynixCacheKey: cacheKey }, "", normalizedPath);
|
|
633
|
+
renderRoute();
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
console.error("[Router] Navigation failed:", err);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function replace(path, props = {}) {
|
|
640
|
+
if (isDestroyed)
|
|
641
|
+
return;
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
if (now - lastNavigationTime < NAVIGATION_RATE_LIMIT) {
|
|
644
|
+
console.warn("[Router] Security: Replace rate limited");
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
lastNavigationTime = now;
|
|
648
|
+
const normalizedPath = normalizePath(path);
|
|
649
|
+
if (!isValidURL(window.location.origin + normalizedPath)) {
|
|
650
|
+
console.error("[Router] Invalid replace URL");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const sanitizedProps = sanitizeProps(props);
|
|
654
|
+
const cacheKey = generateCacheKey();
|
|
655
|
+
addToCache(propsCache, cacheKey, sanitizedProps);
|
|
656
|
+
try {
|
|
657
|
+
window.history.replaceState({ __fynixCacheKey: cacheKey }, "", normalizedPath);
|
|
658
|
+
renderRoute();
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
console.error("[Router] Replace failed:", err);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function back() {
|
|
665
|
+
if (isDestroyed)
|
|
666
|
+
return;
|
|
667
|
+
try {
|
|
668
|
+
window.history.back();
|
|
669
|
+
}
|
|
670
|
+
catch (err) {
|
|
671
|
+
console.error("[Router] Back navigation failed:", err);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const clickHandler = (e) => {
|
|
675
|
+
if (isDestroyed)
|
|
676
|
+
return;
|
|
677
|
+
const target = e.target;
|
|
678
|
+
const link = target.closest("a[data-fynix-link]");
|
|
679
|
+
if (!link)
|
|
680
|
+
return;
|
|
681
|
+
const href = link.getAttribute("href");
|
|
682
|
+
if (!href) {
|
|
683
|
+
console.warn("[Router] Missing href attribute");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (isExternal(href)) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const fullUrl = new URL(link.href, window.location.origin).href;
|
|
690
|
+
if (!isValidURL(fullUrl)) {
|
|
691
|
+
console.warn("[Router] Invalid link href");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
const path = normalizePath(new URL(link.href, window.location.origin).pathname);
|
|
696
|
+
if (path === currentPath)
|
|
697
|
+
return;
|
|
698
|
+
let props = {};
|
|
699
|
+
const propsKey = link.getAttribute("data-props-key");
|
|
700
|
+
if (propsKey &&
|
|
701
|
+
typeof propsKey === "string" &&
|
|
702
|
+
!propsKey.startsWith("__")) {
|
|
703
|
+
if (window[PROPS_NAMESPACE]?.[propsKey]) {
|
|
704
|
+
props = window[PROPS_NAMESPACE][propsKey];
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const serializableProps = {};
|
|
708
|
+
for (const [k, v] of Object.entries(props)) {
|
|
709
|
+
if (typeof k !== "string" || k.startsWith("__"))
|
|
710
|
+
continue;
|
|
711
|
+
serializableProps[k] =
|
|
712
|
+
v && (v._isNixState || v._isRestState) ? v.value : v;
|
|
713
|
+
}
|
|
714
|
+
const cacheKey = generateCacheKey();
|
|
715
|
+
addToCache(propsCache, cacheKey, serializableProps);
|
|
716
|
+
try {
|
|
717
|
+
window.history.pushState({ __fynixCacheKey: cacheKey, serializedProps: serializableProps }, "", path);
|
|
718
|
+
renderRoute();
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
console.error("[Router] Link navigation failed:", err);
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
if (listenerCount < MAX_LISTENERS && !isRouterInitialized) {
|
|
725
|
+
document.addEventListener("click", clickHandler);
|
|
726
|
+
listeners.push({
|
|
727
|
+
element: document,
|
|
728
|
+
event: "click",
|
|
729
|
+
handler: clickHandler,
|
|
730
|
+
});
|
|
731
|
+
listenerCount++;
|
|
732
|
+
window.addEventListener("popstate", renderRoute);
|
|
733
|
+
listeners.push({
|
|
734
|
+
element: window,
|
|
735
|
+
event: "popstate",
|
|
736
|
+
handler: renderRoute,
|
|
737
|
+
});
|
|
738
|
+
listenerCount++;
|
|
739
|
+
}
|
|
740
|
+
function mountRouter(selector = "#app-root") {
|
|
741
|
+
if (isDestroyed) {
|
|
742
|
+
console.error("[Router] Cannot mount destroyed router");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (typeof selector !== "string" || selector.length === 0) {
|
|
746
|
+
console.error("[Router] Invalid selector");
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
rootSelector = selector;
|
|
750
|
+
renderRoute();
|
|
751
|
+
isRouterInitialized = true;
|
|
752
|
+
}
|
|
753
|
+
function cleanup() {
|
|
754
|
+
if (renderTimeout) {
|
|
755
|
+
clearTimeout(renderTimeout);
|
|
756
|
+
renderTimeout = null;
|
|
757
|
+
}
|
|
758
|
+
isDestroyed = true;
|
|
759
|
+
enterpriseRouter.clearCache();
|
|
760
|
+
layoutRouter.cleanup();
|
|
761
|
+
listeners.forEach(({ element, event, handler }) => {
|
|
762
|
+
try {
|
|
763
|
+
element.removeEventListener(event, handler);
|
|
764
|
+
}
|
|
765
|
+
catch (e) {
|
|
766
|
+
console.error("[Router] Cleanup error:", e);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
listeners.length = 0;
|
|
770
|
+
listenerCount = 0;
|
|
771
|
+
propsCache.forEach((props) => {
|
|
772
|
+
if (props && typeof props === "object") {
|
|
773
|
+
Object.values(props).forEach((val) => {
|
|
774
|
+
if (val && typeof val === "object" && "cleanup" in val) {
|
|
775
|
+
try {
|
|
776
|
+
val.cleanup();
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
propsCache.clear();
|
|
785
|
+
if (window[PROPS_NAMESPACE]) {
|
|
786
|
+
const ns = window[PROPS_NAMESPACE];
|
|
787
|
+
if (ns && typeof ns === "object") {
|
|
788
|
+
Object.keys(ns).forEach((key) => {
|
|
789
|
+
delete ns[key];
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
delete window[PROPS_NAMESPACE];
|
|
793
|
+
}
|
|
794
|
+
if (window.__lastRouteProps) {
|
|
795
|
+
delete window.__lastRouteProps;
|
|
796
|
+
}
|
|
797
|
+
isRouterInitialized = false;
|
|
798
|
+
routerInstance = null;
|
|
799
|
+
console.log("[Router] Cleanup complete");
|
|
800
|
+
}
|
|
801
|
+
if (import.meta.hot) {
|
|
802
|
+
import.meta.hot.accept(() => {
|
|
803
|
+
console.log("[Router] HMR detected, re-rendering route...");
|
|
804
|
+
renderRoute();
|
|
805
|
+
});
|
|
806
|
+
import.meta.hot.dispose(() => {
|
|
807
|
+
console.log("[Router] HMR dispose, cleaning up...");
|
|
808
|
+
cleanup();
|
|
809
|
+
routerInstance = null;
|
|
810
|
+
isRouterInitialized = false;
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const router = {
|
|
814
|
+
mountRouter,
|
|
815
|
+
navigate,
|
|
816
|
+
replace,
|
|
817
|
+
back,
|
|
818
|
+
cleanup,
|
|
819
|
+
routes,
|
|
820
|
+
dynamicRoutes,
|
|
821
|
+
preloadRoute: enterpriseRouter.preloadRoute.bind(enterpriseRouter),
|
|
822
|
+
clearCache: () => {
|
|
823
|
+
enterpriseRouter.clearCache();
|
|
824
|
+
layoutRouter.cleanup();
|
|
825
|
+
},
|
|
826
|
+
enableNestedRouting: (nestedRoutes) => {
|
|
827
|
+
router.nestedRoutes = nestedRoutes;
|
|
828
|
+
console.log("[Router] Nested routing enabled with", nestedRoutes.length, "routes");
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
routerInstance = router;
|
|
832
|
+
return router;
|
|
833
|
+
}
|
|
834
|
+
export { createFynix };
|
|
835
|
+
export default createFynix;
|
|
836
|
+
export function setLinkProps(key, props) {
|
|
837
|
+
if (typeof key !== "string" || key.startsWith("__")) {
|
|
838
|
+
console.error("[Router] Invalid props key");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (!props || typeof props !== "object") {
|
|
842
|
+
console.error("[Router] Invalid props object");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (!window[PROPS_NAMESPACE]) {
|
|
846
|
+
window[PROPS_NAMESPACE] = {};
|
|
847
|
+
}
|
|
848
|
+
if (Object.keys(window[PROPS_NAMESPACE]).length >= MAX_CACHE_SIZE) {
|
|
849
|
+
console.warn("[Router] Props storage limit reached");
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
window[PROPS_NAMESPACE][key] = props;
|
|
853
|
+
}
|
|
854
|
+
export function clearLinkProps(key) {
|
|
855
|
+
if (typeof key !== "string")
|
|
856
|
+
return;
|
|
857
|
+
if (window[PROPS_NAMESPACE]?.[key]) {
|
|
858
|
+
const props = window[PROPS_NAMESPACE][key];
|
|
859
|
+
if (props && typeof props === "object") {
|
|
860
|
+
Object.values(props).forEach((val) => {
|
|
861
|
+
if (val && typeof val === "object" && "cleanup" in val) {
|
|
862
|
+
try {
|
|
863
|
+
val.cleanup();
|
|
864
|
+
}
|
|
865
|
+
catch (e) {
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
delete window[PROPS_NAMESPACE][key];
|
|
871
|
+
}
|
|
872
|
+
}
|