lightview 2.0.7 → 2.0.9
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/README.md +47 -1283
- package/components/actions/button.js +5 -5
- package/components/actions/dropdown.js +6 -6
- package/components/actions/modal.js +9 -9
- package/components/actions/swap.js +5 -5
- package/components/data-display/accordion.js +6 -6
- package/components/data-display/alert.js +6 -6
- package/components/data-display/avatar.js +7 -7
- package/components/data-display/badge.js +5 -5
- package/components/data-display/card.js +7 -7
- package/components/data-display/carousel.js +4 -4
- package/components/data-display/chart.js +8 -8
- package/components/data-display/chat.js +7 -7
- package/components/data-display/collapse.js +5 -5
- package/components/data-display/countdown.js +3 -3
- package/components/data-display/diff.js +6 -6
- package/components/data-display/kbd.js +5 -5
- package/components/data-display/loading.js +5 -5
- package/components/data-display/progress.js +5 -5
- package/components/data-display/radial-progress.js +5 -5
- package/components/data-display/skeleton.js +3 -3
- package/components/data-display/stats.js +9 -9
- package/components/data-display/table.js +9 -9
- package/components/data-display/timeline.js +8 -8
- package/components/data-display/toast.js +3 -3
- package/components/data-display/tooltip.js +3 -3
- package/components/data-input/checkbox.js +5 -5
- package/components/data-input/file-input.js +3 -3
- package/components/data-input/input.js +5 -5
- package/components/data-input/radio.js +9 -9
- package/components/data-input/range.js +3 -3
- package/components/data-input/rating.js +3 -3
- package/components/data-input/select.js +5 -5
- package/components/data-input/textarea.js +3 -3
- package/components/data-input/toggle.js +5 -5
- package/components/layout/divider.js +3 -3
- package/components/layout/drawer.js +7 -7
- package/components/layout/footer.js +5 -5
- package/components/layout/hero.js +5 -5
- package/components/layout/indicator.js +4 -4
- package/components/layout/join.js +4 -4
- package/components/layout/navbar.js +6 -6
- package/components/navigation/breadcrumbs.js +4 -4
- package/components/navigation/dock.js +5 -5
- package/components/navigation/menu.js +6 -6
- package/components/navigation/pagination.js +3 -3
- package/components/navigation/steps.js +4 -4
- package/components/navigation/tabs.js +5 -5
- package/components/theme/theme-switch.js +30 -30
- package/docs/about.html +142 -14
- package/docs/api/computed.html +1 -6
- package/docs/api/effects.html +1 -7
- package/docs/api/elements.html +6 -10
- package/docs/api/enhance.html +1 -6
- package/docs/api/hypermedia.html +154 -22
- package/docs/api/index.html +7 -12
- package/docs/api/nav.html +18 -1
- package/docs/api/signals.html +1 -6
- package/docs/api/state.html +1 -6
- package/docs/assets/js/examplify-sandbox.html +2 -2
- package/docs/assets/js/examplify.js +15 -15
- package/docs/components/accordion.html +4 -4
- package/docs/components/alert.html +4 -4
- package/docs/components/avatar.html +4 -4
- package/docs/components/badge.html +4 -4
- package/docs/components/breadcrumbs.html +3 -3
- package/docs/components/button.html +5 -5
- package/docs/components/card.html +4 -4
- package/docs/components/carousel.html +3 -3
- package/docs/components/chart-area.html +6 -6
- package/docs/components/chart-bar.html +6 -6
- package/docs/components/chart-column.html +6 -6
- package/docs/components/chart-line.html +6 -6
- package/docs/components/chart-pie.html +6 -6
- package/docs/components/chart.html +2 -2
- package/docs/components/chat.html +4 -4
- package/docs/components/checkbox.html +4 -4
- package/docs/components/collapse.html +4 -4
- package/docs/components/countdown.html +4 -4
- package/docs/components/diff.html +3 -3
- package/docs/components/divider.html +3 -3
- package/docs/components/dock.html +3 -3
- package/docs/components/drawer.html +4 -4
- package/docs/components/dropdown.html +4 -4
- package/docs/components/file-input.html +4 -4
- package/docs/components/footer.html +3 -3
- package/docs/components/gallery.html +2 -2
- package/docs/components/hero.html +3 -3
- package/docs/components/index.css +5 -3
- package/docs/components/index.html +4 -4
- package/docs/components/indicator.html +3 -3
- package/docs/components/input.html +4 -4
- package/docs/components/join.html +3 -3
- package/docs/components/kbd.html +3 -3
- package/docs/components/loading.html +4 -4
- package/docs/components/menu.html +4 -4
- package/docs/components/modal.html +4 -4
- package/docs/components/navbar.html +3 -3
- package/docs/components/pagination.html +3 -3
- package/docs/components/progress.html +4 -4
- package/docs/components/radial-progress.html +3 -3
- package/docs/components/radio.html +4 -4
- package/docs/components/range.html +4 -4
- package/docs/components/rating.html +4 -4
- package/docs/components/select.html +4 -4
- package/docs/components/sidebar-setup.js +1 -1
- package/docs/components/skeleton.html +4 -4
- package/docs/components/spinner.html +4 -4
- package/docs/components/stats.html +4 -4
- package/docs/components/steps.html +3 -3
- package/docs/components/swap.html +4 -4
- package/docs/components/switch.html +4 -4
- package/docs/components/table.html +4 -4
- package/docs/components/tabs.html +4 -4
- package/docs/components/text-input.html +4 -4
- package/docs/components/textarea.html +4 -4
- package/docs/components/timeline.html +4 -4
- package/docs/components/toast.html +4 -4
- package/docs/components/toggle.html +4 -4
- package/docs/components/tooltip.html +4 -4
- package/docs/examples/getting-started-example.html +1 -1
- package/docs/examples/index.html +1 -2
- package/docs/getting-started/index.html +105 -14
- package/docs/index.html +2 -11
- package/docs/router-nav.html +13 -0
- package/docs/router.html +60 -17
- package/docs/styles/index.html +2 -7
- package/docs/syntax.html +144 -0
- package/functions/_middleware.js +17 -10
- package/functions/processServerScripts.js +127 -0
- package/index.html +8 -8
- package/lightview-router.js +141 -297
- package/lightview-x.js +604 -573
- package/lightview.js +179 -157
- package/package.json +33 -26
- package/scripts/analysis/README.md +2 -0
- package/scripts/analysis/analyze.js +266 -0
- package/scripts/analysis/latest_metrics.md +185 -0
- package/wrangler.toml +6 -0
- package/docs/playground.html +0 -416
package/lightview-router.js
CHANGED
|
@@ -1,364 +1,208 @@
|
|
|
1
1
|
(() => {
|
|
2
|
+
/**
|
|
3
|
+
* LIGHTVIEW ROUTER
|
|
4
|
+
* A lightweight, pipeline-based History API router with middleware support.
|
|
5
|
+
*/
|
|
2
6
|
// ============= LIGHTVIEW ROUTER =============
|
|
3
7
|
// Pipeline-based History API router with middleware support
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {string} shellPath - Relative path to the shell (e.g., '/index.html')
|
|
10
|
+
* Shell-based routing helper. If the 'content' element is missing,
|
|
11
|
+
* redirects to a shell path with the current path in the 'load' query parameter.
|
|
9
12
|
*/
|
|
10
13
|
const base = (shellPath) => {
|
|
11
|
-
if (typeof window === 'undefined') return;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (inShell) return;
|
|
16
|
-
|
|
17
|
-
// Get current path relative to domain root
|
|
18
|
-
const currentPath = window.location.pathname;
|
|
19
|
-
|
|
20
|
-
// Build shell URL with load parameter
|
|
21
|
-
const shellUrl = new URL(shellPath, window.location.href);
|
|
22
|
-
shellUrl.searchParams.set('load', currentPath);
|
|
23
|
-
|
|
24
|
-
// Redirect to shell
|
|
25
|
-
window.location.href = shellUrl.toString();
|
|
14
|
+
if (typeof window === 'undefined' || document.getElementById('content')) return;
|
|
15
|
+
const url = new URL(shellPath, globalThis.location.href);
|
|
16
|
+
url.searchParams.set('load', globalThis.location.pathname);
|
|
17
|
+
globalThis.location.href = url.toString();
|
|
26
18
|
};
|
|
27
19
|
|
|
28
20
|
/**
|
|
29
|
-
*
|
|
21
|
+
* Creates a new router instance.
|
|
22
|
+
* @param {Object} options - Router configuration.
|
|
30
23
|
*/
|
|
31
24
|
const router = (options = {}) => {
|
|
32
|
-
const {
|
|
33
|
-
base = '',
|
|
34
|
-
contentEl = null,
|
|
35
|
-
notFound = null,
|
|
36
|
-
debug = false,
|
|
37
|
-
onResponse = null,
|
|
38
|
-
onStart = null
|
|
39
|
-
} = options;
|
|
40
|
-
|
|
25
|
+
const { base = '', contentEl, notFound, debug, onResponse, onStart } = options;
|
|
41
26
|
const chains = [];
|
|
42
27
|
|
|
43
28
|
/**
|
|
44
|
-
*
|
|
29
|
+
* Normalizes paths by adding leading slash and removing trailing slash.
|
|
45
30
|
*/
|
|
46
|
-
const normalizePath = (
|
|
47
|
-
if (!
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const url = new URL(path, window.location.origin);
|
|
53
|
-
path = url.pathname;
|
|
54
|
-
} catch (e) {
|
|
55
|
-
// Invalid URL, treat as path
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (base && path.startsWith(base)) {
|
|
60
|
-
path = path.slice(base.length);
|
|
31
|
+
const normalizePath = (p) => {
|
|
32
|
+
if (!p) return '/';
|
|
33
|
+
let hash = '';
|
|
34
|
+
if (p.includes('#')) {
|
|
35
|
+
[p, hash] = p.split('#');
|
|
36
|
+
hash = '#' + hash;
|
|
61
37
|
}
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (path.length > 1 && path.endsWith('/')) {
|
|
66
|
-
path = path.slice(0, -1);
|
|
67
|
-
}
|
|
68
|
-
return path;
|
|
38
|
+
try { if (p.startsWith('http') || p.startsWith('//')) p = new URL(p, globalThis.location.origin).pathname; } catch (e) { /* Invalid URL */ }
|
|
39
|
+
if (base && p.startsWith(base)) p = p.slice(base.length);
|
|
40
|
+
return (p.replace(/\/+$/, '').replace(/^([^/])/, '/$1') || '/') + hash;
|
|
69
41
|
};
|
|
70
42
|
|
|
71
|
-
/**
|
|
72
|
-
* Convert a matcher (string/regexp) into a function
|
|
73
|
-
* Returns: (input) => params OR null (if no match)
|
|
74
|
-
*/
|
|
75
43
|
const createMatcher = (pattern) => {
|
|
76
|
-
if (pattern
|
|
77
|
-
return (ctx) => {
|
|
78
|
-
const path = typeof ctx === 'string' ? ctx : ctx.path;
|
|
79
|
-
const match = path.match(pattern);
|
|
80
|
-
return match ? { match, ...ctx } : null;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (typeof pattern === 'string') {
|
|
85
|
-
return (ctx) => {
|
|
86
|
-
const path = typeof ctx === 'string' ? ctx : ctx.path;
|
|
87
|
-
|
|
88
|
-
// Specific check: if pattern is exactly '*', match everything
|
|
89
|
-
if (pattern === '*') return { path, wildcard: path, ...ctx };
|
|
90
|
-
|
|
91
|
-
// Exact match
|
|
92
|
-
if (pattern === path) return { path, ...ctx };
|
|
93
|
-
|
|
94
|
-
// Wildcard /api/*
|
|
95
|
-
if (pattern.includes('*')) {
|
|
96
|
-
const regexStr = '^' + pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '(.*)') + '$';
|
|
97
|
-
const regex = new RegExp(regexStr);
|
|
98
|
-
const match = path.match(regex);
|
|
99
|
-
if (match) {
|
|
100
|
-
return { path, wildcard: match[1], ...ctx };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Named params /user/:id
|
|
105
|
-
if (pattern.includes(':')) {
|
|
106
|
-
const keys = [];
|
|
107
|
-
const regexStr = '^' + pattern.replace(/:([^/]+)/g, (_, key) => {
|
|
108
|
-
keys.push(key);
|
|
109
|
-
return '([^/]+)';
|
|
110
|
-
}) + '$';
|
|
111
|
-
const match = path.match(new RegExp(regexStr));
|
|
112
|
-
|
|
113
|
-
if (match) {
|
|
114
|
-
const params = {};
|
|
115
|
-
keys.forEach((key, i) => {
|
|
116
|
-
params[key] = match[i + 1];
|
|
117
|
-
});
|
|
118
|
-
return { path, params, ...ctx };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return pattern; // Already a function
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Convert a replacement string into a function
|
|
131
|
-
* Returns: (ctx) => updated context with new path
|
|
132
|
-
*/
|
|
133
|
-
const createReplacer = (pattern) => {
|
|
44
|
+
if (typeof pattern === 'function') return pattern;
|
|
134
45
|
return (ctx) => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
46
|
+
const { path } = ctx;
|
|
47
|
+
const pathOnly = path.split('#')[0];
|
|
48
|
+
if (pattern instanceof RegExp) {
|
|
49
|
+
const m = pathOnly.match(pattern);
|
|
50
|
+
return m ? { ...ctx, match: m } : null;
|
|
138
51
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
52
|
+
if (pattern === '*' || pattern === pathOnly) return { ...ctx, wildcard: pathOnly };
|
|
53
|
+
|
|
54
|
+
const keys = [];
|
|
55
|
+
const regexStr = '^' + pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
56
|
+
.replace(/\\\*/g, '(.*)')
|
|
57
|
+
.replace(/:([^/]+)/g, (_, k) => (keys.push(k), '([^/]+)')) + '$';
|
|
58
|
+
const m = pathOnly.match(new RegExp(regexStr));
|
|
59
|
+
if (m) {
|
|
60
|
+
const params = {};
|
|
61
|
+
keys.forEach((k, i) => params[k] = m[i + 1]);
|
|
62
|
+
return { ...ctx, params, wildcard: m[1] };
|
|
143
63
|
}
|
|
144
|
-
|
|
145
|
-
return { ...ctx, path: newPath };
|
|
64
|
+
return null;
|
|
146
65
|
};
|
|
147
66
|
};
|
|
148
67
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
68
|
+
const createReplacer = (pat) => (ctx) => {
|
|
69
|
+
const [path, hash] = ctx.path.split('#');
|
|
70
|
+
return {
|
|
71
|
+
...ctx,
|
|
72
|
+
path: pat.replace(/\*|:([^/]+)/g, (m, k) => (k ? ctx.params?.[k] : ctx.wildcard) || m) + (hash ? '#' + hash : '')
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const fetchHandler = async (ctx) => {
|
|
155
77
|
try {
|
|
156
|
-
const
|
|
78
|
+
const pathOnly = ctx.path.split('#')[0];
|
|
79
|
+
const res = await fetch(pathOnly);
|
|
157
80
|
if (res.ok) return res;
|
|
158
|
-
} catch (e) {
|
|
159
|
-
if (debug) console.error('[Router] Fetch error:', e);
|
|
160
|
-
}
|
|
81
|
+
} catch (e) { if (debug) console.error('[Router] Fetch error:', e); }
|
|
161
82
|
return null;
|
|
162
83
|
};
|
|
163
84
|
|
|
164
85
|
/**
|
|
165
|
-
*
|
|
166
|
-
* usage: router.use(pattern, replacement, handler, ...)
|
|
167
|
-
*
|
|
168
|
-
* If contentEl is set and the chain ends with a string (path) or has no handlers,
|
|
169
|
-
* the router automatically appends a fetch handler.
|
|
86
|
+
* Adds a route or middleware to the router's pipeline.
|
|
170
87
|
*/
|
|
171
88
|
const use = (...args) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const firstArg = args[0];
|
|
175
|
-
|
|
176
|
-
if (typeof firstArg !== 'function') {
|
|
177
|
-
chain.push(createMatcher(firstArg));
|
|
178
|
-
} else {
|
|
179
|
-
chain.push(firstArg);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
let hasCustomHandler = false;
|
|
183
|
-
for (let i = 1; i < args.length; i++) {
|
|
184
|
-
const arg = args[i];
|
|
185
|
-
if (typeof arg === 'string') {
|
|
186
|
-
chain.push(createReplacer(arg));
|
|
187
|
-
} else if (typeof arg === 'function') {
|
|
188
|
-
chain.push(arg);
|
|
189
|
-
hasCustomHandler = true;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// If contentEl is set and no custom handler provided, append default fetch
|
|
194
|
-
if (contentEl && !hasCustomHandler) {
|
|
195
|
-
chain.push(defaultFetchHandler);
|
|
196
|
-
}
|
|
197
|
-
|
|
89
|
+
const chain = args.map((arg, i) => (i === 0 && typeof arg !== 'function') ? createMatcher(arg) : (typeof arg === 'string' ? createReplacer(arg) : arg));
|
|
90
|
+
if (contentEl && !chain.some(f => f.name === 'fetchHandler' || args.some(a => typeof a === 'function'))) chain.push(fetchHandler);
|
|
198
91
|
chains.push(chain);
|
|
199
92
|
return routerInstance;
|
|
200
93
|
};
|
|
201
94
|
|
|
202
95
|
/**
|
|
203
|
-
*
|
|
96
|
+
* Processes a path through the registered chains.
|
|
204
97
|
*/
|
|
205
|
-
const route = async (
|
|
206
|
-
let
|
|
207
|
-
|
|
208
|
-
let context = { path: currentPath, contentEl };
|
|
209
|
-
|
|
210
|
-
if (debug) console.log(`[Router] Routing: ${currentPath}`);
|
|
98
|
+
const route = async (raw) => {
|
|
99
|
+
let ctx = { path: normalizePath(raw), contentEl };
|
|
100
|
+
if (debug) console.log(`[Router] Routing: ${ctx.path}`);
|
|
211
101
|
|
|
212
102
|
for (const chain of chains) {
|
|
213
|
-
let
|
|
214
|
-
let chainFailed = false;
|
|
215
|
-
|
|
103
|
+
let res = ctx, failed = false;
|
|
216
104
|
for (const fn of chain) {
|
|
217
105
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
chainFailed = true;
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
chainResult = result;
|
|
227
|
-
} catch (err) {
|
|
228
|
-
console.error('[Router] Error in route chain:', err);
|
|
229
|
-
chainFailed = true;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (!chainFailed) {
|
|
235
|
-
// Fallthrough with updated context
|
|
236
|
-
if (typeof chainResult === 'string') {
|
|
237
|
-
context = { path: chainResult, contentEl };
|
|
238
|
-
if (debug) console.log(`[Router] Path updated to: ${chainResult}`);
|
|
239
|
-
} else if (chainResult && chainResult.path) {
|
|
240
|
-
context = chainResult;
|
|
241
|
-
// Ensure contentEl is preserved if not in result
|
|
242
|
-
if (!context.contentEl) context.contentEl = contentEl;
|
|
243
|
-
}
|
|
106
|
+
res = await fn(res);
|
|
107
|
+
if (res instanceof Response) return res;
|
|
108
|
+
if (!res) { failed = true; break; }
|
|
109
|
+
} catch (e) { console.error('[Router] Chain error:', e); failed = true; break; }
|
|
244
110
|
}
|
|
111
|
+
if (!failed) ctx = typeof res === 'string' ? { ...ctx, path: res } : { ...ctx, ...res };
|
|
245
112
|
}
|
|
246
|
-
|
|
247
|
-
if (notFound) return notFound(context);
|
|
248
|
-
return null;
|
|
113
|
+
return notFound ? notFound(ctx) : null;
|
|
249
114
|
};
|
|
250
115
|
|
|
251
116
|
const handleRequest = async (path) => {
|
|
252
117
|
if (onStart) onStart(path);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const html = await response.text();
|
|
264
|
-
contentEl.innerHTML = html;
|
|
265
|
-
|
|
266
|
-
// Re-execute scripts in the loaded content
|
|
267
|
-
const scripts = contentEl.querySelectorAll('script');
|
|
268
|
-
scripts.forEach(script => {
|
|
269
|
-
const newScript = document.createElement('script');
|
|
270
|
-
if (script.type) newScript.type = script.type;
|
|
271
|
-
if (script.src) {
|
|
272
|
-
newScript.src = script.src;
|
|
273
|
-
} else {
|
|
274
|
-
newScript.textContent = script.textContent;
|
|
275
|
-
}
|
|
276
|
-
script.parentNode.replaceChild(newScript, script);
|
|
118
|
+
const res = await route(path);
|
|
119
|
+
if (!res) return console.warn(`[Router] No route: ${path}`);
|
|
120
|
+
|
|
121
|
+
if (res.ok && contentEl) {
|
|
122
|
+
contentEl.innerHTML = await res.text();
|
|
123
|
+
contentEl.querySelectorAll('script').forEach(s => {
|
|
124
|
+
const n = document.createElement('script');
|
|
125
|
+
[...s.attributes].forEach(a => n.setAttribute(a.name, a.value));
|
|
126
|
+
n.textContent = s.textContent;
|
|
127
|
+
s.replaceWith(n);
|
|
277
128
|
});
|
|
278
|
-
}
|
|
279
129
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
130
|
+
// Handle hash scrolling if present in the target path
|
|
131
|
+
const urlParts = path.split('#');
|
|
132
|
+
const hash = urlParts.length > 1 ? '#' + urlParts[1] : '';
|
|
133
|
+
if (hash) {
|
|
134
|
+
requestAnimationFrame(() => {
|
|
135
|
+
requestAnimationFrame(() => {
|
|
136
|
+
const id = hash.slice(1);
|
|
137
|
+
const target = document.getElementById(id);
|
|
138
|
+
if (target) {
|
|
139
|
+
target.style.scrollMarginTop = 'calc(var(--site-nav-height, 0px) + 2rem)';
|
|
140
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
283
145
|
}
|
|
284
|
-
|
|
285
|
-
return
|
|
146
|
+
if (onResponse) await onResponse(res, path);
|
|
147
|
+
return res;
|
|
286
148
|
};
|
|
287
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Navigates to a new path and updates the browser history.
|
|
152
|
+
*/
|
|
288
153
|
const navigate = (path) => {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
const u = new URL(dest, window.location.origin);
|
|
296
|
-
dest = u.pathname + u.search + u.hash;
|
|
297
|
-
} catch (e) { }
|
|
154
|
+
const p = normalizePath(path);
|
|
155
|
+
return handleRequest(base + p).then(r => {
|
|
156
|
+
let dest = r?.url ? new URL(r.url, globalThis.location.origin).pathname : base + p;
|
|
157
|
+
// If the original path had a hash, ensure it's preserved in the history state
|
|
158
|
+
if (p.includes('#') && !dest.includes('#')) {
|
|
159
|
+
dest += '#' + p.split('#')[1];
|
|
298
160
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
window.history.pushState({ path: dest }, '', dest);
|
|
302
|
-
}).catch((err) => {
|
|
303
|
-
console.error('[Router] Error handling request:', err);
|
|
304
|
-
})
|
|
161
|
+
globalThis.history.pushState({ path: dest }, '', dest);
|
|
162
|
+
}).catch(e => console.error('[Router] Nav error:', e));
|
|
305
163
|
};
|
|
306
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Starts the router by handling the initial path and setting up event listeners.
|
|
167
|
+
*/
|
|
307
168
|
const start = async () => {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
e.preventDefault();
|
|
327
|
-
const url = new URL(href, document.baseURI);
|
|
328
|
-
const path = normalizePath(url.pathname);
|
|
329
|
-
navigate(path);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
if (loadPath) {
|
|
333
|
-
window.history.replaceState({ path: loadPath }, '', loadPath);
|
|
334
|
-
handleRequest(loadPath);
|
|
335
|
-
} else {
|
|
336
|
-
const initialPath = normalizePath(window.location.pathname);
|
|
337
|
-
window.history.replaceState({ path: initialPath }, '', base + initialPath);
|
|
338
|
-
handleRequest(initialPath);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return routerInstance;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const routerInstance = {
|
|
345
|
-
use,
|
|
346
|
-
navigate,
|
|
347
|
-
start
|
|
169
|
+
const load = new URLSearchParams(globalThis.location.search).get('load');
|
|
170
|
+
globalThis.onpopstate = (e) => handleRequest(e.state?.path || normalizePath(globalThis.location.pathname + globalThis.location.hash));
|
|
171
|
+
document.onclick = (e) => {
|
|
172
|
+
const a = e.target.closest('a[href]');
|
|
173
|
+
if (!a || a.target === '_blank' || /^(http|#|mailto|tel)/.test(a.getAttribute('href'))) return;
|
|
174
|
+
|
|
175
|
+
// Only intercept internal links
|
|
176
|
+
const url = new URL(a.href, document.baseURI);
|
|
177
|
+
if (url.origin === globalThis.location.origin) {
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
const fullPath = url.pathname + url.search + url.hash;
|
|
180
|
+
navigate(normalizePath(fullPath));
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const init = load || normalizePath(globalThis.location.pathname + globalThis.location.hash);
|
|
184
|
+
globalThis.history.replaceState({ path: init }, '', base + init);
|
|
185
|
+
return handleRequest(init).then(() => routerInstance);
|
|
348
186
|
};
|
|
349
187
|
|
|
188
|
+
const routerInstance = { use, navigate, start };
|
|
350
189
|
return routerInstance;
|
|
351
190
|
};
|
|
352
191
|
|
|
353
|
-
const LightviewRouter = {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
192
|
+
const LightviewRouter = { base, router };
|
|
193
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = LightviewRouter;
|
|
194
|
+
else if (typeof window !== 'undefined') {
|
|
195
|
+
globalThis.LightviewRouter = LightviewRouter;
|
|
196
|
+
|
|
197
|
+
// Auto-initialize base path from script URL if present
|
|
198
|
+
// Use case: <script src="/lightview-router.js?base=/index.html"></script>
|
|
199
|
+
try {
|
|
200
|
+
const script = document.currentScript;
|
|
201
|
+
if (script && script.src.includes('?')) {
|
|
202
|
+
const params = new URL(script.src).searchParams;
|
|
203
|
+
const b = params.get('base');
|
|
204
|
+
if (b) LightviewRouter.base(b);
|
|
205
|
+
}
|
|
206
|
+
} catch (e) { /* ignore */ }
|
|
363
207
|
}
|
|
364
208
|
})();
|