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.
Files changed (140) hide show
  1. package/README.md +47 -1283
  2. package/components/actions/button.js +5 -5
  3. package/components/actions/dropdown.js +6 -6
  4. package/components/actions/modal.js +9 -9
  5. package/components/actions/swap.js +5 -5
  6. package/components/data-display/accordion.js +6 -6
  7. package/components/data-display/alert.js +6 -6
  8. package/components/data-display/avatar.js +7 -7
  9. package/components/data-display/badge.js +5 -5
  10. package/components/data-display/card.js +7 -7
  11. package/components/data-display/carousel.js +4 -4
  12. package/components/data-display/chart.js +8 -8
  13. package/components/data-display/chat.js +7 -7
  14. package/components/data-display/collapse.js +5 -5
  15. package/components/data-display/countdown.js +3 -3
  16. package/components/data-display/diff.js +6 -6
  17. package/components/data-display/kbd.js +5 -5
  18. package/components/data-display/loading.js +5 -5
  19. package/components/data-display/progress.js +5 -5
  20. package/components/data-display/radial-progress.js +5 -5
  21. package/components/data-display/skeleton.js +3 -3
  22. package/components/data-display/stats.js +9 -9
  23. package/components/data-display/table.js +9 -9
  24. package/components/data-display/timeline.js +8 -8
  25. package/components/data-display/toast.js +3 -3
  26. package/components/data-display/tooltip.js +3 -3
  27. package/components/data-input/checkbox.js +5 -5
  28. package/components/data-input/file-input.js +3 -3
  29. package/components/data-input/input.js +5 -5
  30. package/components/data-input/radio.js +9 -9
  31. package/components/data-input/range.js +3 -3
  32. package/components/data-input/rating.js +3 -3
  33. package/components/data-input/select.js +5 -5
  34. package/components/data-input/textarea.js +3 -3
  35. package/components/data-input/toggle.js +5 -5
  36. package/components/layout/divider.js +3 -3
  37. package/components/layout/drawer.js +7 -7
  38. package/components/layout/footer.js +5 -5
  39. package/components/layout/hero.js +5 -5
  40. package/components/layout/indicator.js +4 -4
  41. package/components/layout/join.js +4 -4
  42. package/components/layout/navbar.js +6 -6
  43. package/components/navigation/breadcrumbs.js +4 -4
  44. package/components/navigation/dock.js +5 -5
  45. package/components/navigation/menu.js +6 -6
  46. package/components/navigation/pagination.js +3 -3
  47. package/components/navigation/steps.js +4 -4
  48. package/components/navigation/tabs.js +5 -5
  49. package/components/theme/theme-switch.js +30 -30
  50. package/docs/about.html +142 -14
  51. package/docs/api/computed.html +1 -6
  52. package/docs/api/effects.html +1 -7
  53. package/docs/api/elements.html +6 -10
  54. package/docs/api/enhance.html +1 -6
  55. package/docs/api/hypermedia.html +154 -22
  56. package/docs/api/index.html +7 -12
  57. package/docs/api/nav.html +18 -1
  58. package/docs/api/signals.html +1 -6
  59. package/docs/api/state.html +1 -6
  60. package/docs/assets/js/examplify-sandbox.html +2 -2
  61. package/docs/assets/js/examplify.js +15 -15
  62. package/docs/components/accordion.html +4 -4
  63. package/docs/components/alert.html +4 -4
  64. package/docs/components/avatar.html +4 -4
  65. package/docs/components/badge.html +4 -4
  66. package/docs/components/breadcrumbs.html +3 -3
  67. package/docs/components/button.html +5 -5
  68. package/docs/components/card.html +4 -4
  69. package/docs/components/carousel.html +3 -3
  70. package/docs/components/chart-area.html +6 -6
  71. package/docs/components/chart-bar.html +6 -6
  72. package/docs/components/chart-column.html +6 -6
  73. package/docs/components/chart-line.html +6 -6
  74. package/docs/components/chart-pie.html +6 -6
  75. package/docs/components/chart.html +2 -2
  76. package/docs/components/chat.html +4 -4
  77. package/docs/components/checkbox.html +4 -4
  78. package/docs/components/collapse.html +4 -4
  79. package/docs/components/countdown.html +4 -4
  80. package/docs/components/diff.html +3 -3
  81. package/docs/components/divider.html +3 -3
  82. package/docs/components/dock.html +3 -3
  83. package/docs/components/drawer.html +4 -4
  84. package/docs/components/dropdown.html +4 -4
  85. package/docs/components/file-input.html +4 -4
  86. package/docs/components/footer.html +3 -3
  87. package/docs/components/gallery.html +2 -2
  88. package/docs/components/hero.html +3 -3
  89. package/docs/components/index.css +5 -3
  90. package/docs/components/index.html +4 -4
  91. package/docs/components/indicator.html +3 -3
  92. package/docs/components/input.html +4 -4
  93. package/docs/components/join.html +3 -3
  94. package/docs/components/kbd.html +3 -3
  95. package/docs/components/loading.html +4 -4
  96. package/docs/components/menu.html +4 -4
  97. package/docs/components/modal.html +4 -4
  98. package/docs/components/navbar.html +3 -3
  99. package/docs/components/pagination.html +3 -3
  100. package/docs/components/progress.html +4 -4
  101. package/docs/components/radial-progress.html +3 -3
  102. package/docs/components/radio.html +4 -4
  103. package/docs/components/range.html +4 -4
  104. package/docs/components/rating.html +4 -4
  105. package/docs/components/select.html +4 -4
  106. package/docs/components/sidebar-setup.js +1 -1
  107. package/docs/components/skeleton.html +4 -4
  108. package/docs/components/spinner.html +4 -4
  109. package/docs/components/stats.html +4 -4
  110. package/docs/components/steps.html +3 -3
  111. package/docs/components/swap.html +4 -4
  112. package/docs/components/switch.html +4 -4
  113. package/docs/components/table.html +4 -4
  114. package/docs/components/tabs.html +4 -4
  115. package/docs/components/text-input.html +4 -4
  116. package/docs/components/textarea.html +4 -4
  117. package/docs/components/timeline.html +4 -4
  118. package/docs/components/toast.html +4 -4
  119. package/docs/components/toggle.html +4 -4
  120. package/docs/components/tooltip.html +4 -4
  121. package/docs/examples/getting-started-example.html +1 -1
  122. package/docs/examples/index.html +1 -2
  123. package/docs/getting-started/index.html +105 -14
  124. package/docs/index.html +2 -11
  125. package/docs/router-nav.html +13 -0
  126. package/docs/router.html +60 -17
  127. package/docs/styles/index.html +2 -7
  128. package/docs/syntax.html +144 -0
  129. package/functions/_middleware.js +17 -10
  130. package/functions/processServerScripts.js +127 -0
  131. package/index.html +8 -8
  132. package/lightview-router.js +141 -297
  133. package/lightview-x.js +604 -573
  134. package/lightview.js +179 -157
  135. package/package.json +33 -26
  136. package/scripts/analysis/README.md +2 -0
  137. package/scripts/analysis/analyze.js +266 -0
  138. package/scripts/analysis/latest_metrics.md +185 -0
  139. package/wrangler.toml +6 -0
  140. package/docs/playground.html +0 -416
@@ -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
- * Shim function for individual pages
7
- * Redirects direct page access to the shell with a load parameter
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
- // Check if we're in the shell or loaded directly
14
- const inShell = document.getElementById('content') !== null;
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
- * Create a new Router instance
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
- * Normalize a path by removing base and trailing slashes
29
+ * Normalizes paths by adding leading slash and removing trailing slash.
45
30
  */
46
- const normalizePath = (path) => {
47
- if (!path) return '/';
48
-
49
- // Handle full URLs
50
- if (path.startsWith('http') || path.startsWith('//')) {
51
- try {
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 (!path.startsWith('/')) {
63
- path = '/' + path;
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 instanceof RegExp) {
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
- let newPath = pattern;
136
- if (ctx.wildcard && newPath.includes('*')) {
137
- newPath = newPath.replace('*', ctx.wildcard);
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 (ctx.params) {
140
- Object.entries(ctx.params).forEach(([key, val]) => {
141
- newPath = newPath.replace(':' + key, val);
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
- // Return updated context instead of just string
145
- return { ...ctx, path: newPath };
64
+ return null;
146
65
  };
147
66
  };
148
67
 
149
- /**
150
- * Default fetch handler - fetches the current path and returns Response
151
- * Uses contentEl from context (allows middleware to override target)
152
- */
153
- const defaultFetchHandler = async (ctx) => {
154
- const path = typeof ctx === 'string' ? ctx : ctx.path;
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 res = await fetch(path);
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
- * Register a route chain
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
- if (args.length === 0) return;
173
- const chain = [];
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
- * Execute routing for a given path
96
+ * Processes a path through the registered chains.
204
97
  */
205
- const route = async (rawPath) => {
206
- let currentPath = normalizePath(rawPath);
207
- // Include contentEl in context for middleware to access/override
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 chainResult = context;
214
- let chainFailed = false;
215
-
103
+ let res = ctx, failed = false;
216
104
  for (const fn of chain) {
217
105
  try {
218
- const result = await fn(chainResult);
219
-
220
- if (result instanceof Response) return result;
221
- if (!result) {
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
- const response = await route(path);
255
-
256
- if (!response) {
257
- console.warn(`[Router] No route handled path: ${path}`);
258
- return null;
259
- }
260
-
261
- // Auto-render to contentEl if provided and response is OK
262
- if (response.ok && contentEl) {
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
- // Call onResponse AFTER auto-render for post-render logic (analytics, scroll, etc.)
281
- if (onResponse) {
282
- await onResponse(response, path);
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 response;
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
- path = normalizePath(path);
290
- let fullPath = base + path;
291
- return handleRequest(fullPath).then((response) => {
292
- let dest = response?.url;
293
- if (dest && (dest.startsWith('http') || dest.startsWith('//'))) {
294
- try {
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
- // Fallback to intent if response has no URL
300
- if (!dest) dest = fullPath;
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 urlParams = new URLSearchParams(window.location.search);
309
- const loadPath = urlParams.get('load');
310
-
311
- window.addEventListener('popstate', (e) => {
312
- const path = e.state?.path || normalizePath(window.location.pathname);
313
- handleRequest(path);
314
- });
315
-
316
- document.addEventListener('click', (e) => {
317
- const link = e.target.closest('a[href]');
318
- if (!link) return;
319
- const href = link.getAttribute('href');
320
- if (
321
- !href || href.startsWith('http') || href.startsWith('//') ||
322
- href.startsWith('#') || href.startsWith('mailto:') ||
323
- link.target === '_blank'
324
- ) return;
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
- base,
355
- router
356
- };
357
-
358
- if (typeof module !== 'undefined' && module.exports) {
359
- module.exports = LightviewRouter;
360
- }
361
- if (typeof window !== 'undefined') {
362
- window.LightviewRouter = LightviewRouter;
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
  })();