vorzelajs 0.0.1

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 (96) hide show
  1. package/README.md +188 -0
  2. package/bin/vorzelajs.mjs +2 -0
  3. package/dist/analytics.d.ts +132 -0
  4. package/dist/analytics.d.ts.map +1 -0
  5. package/dist/analytics.js +690 -0
  6. package/dist/cli/build.d.ts +2 -0
  7. package/dist/cli/build.d.ts.map +1 -0
  8. package/dist/cli/build.js +22 -0
  9. package/dist/cli/dev.d.ts +2 -0
  10. package/dist/cli/dev.d.ts.map +1 -0
  11. package/dist/cli/dev.js +93 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +29 -0
  15. package/dist/cli/serve.d.ts +2 -0
  16. package/dist/cli/serve.d.ts.map +1 -0
  17. package/dist/cli/serve.js +43 -0
  18. package/dist/cookie.d.ts +33 -0
  19. package/dist/cookie.d.ts.map +1 -0
  20. package/dist/cookie.js +198 -0
  21. package/dist/debug/error-stack.d.ts +14 -0
  22. package/dist/debug/error-stack.d.ts.map +1 -0
  23. package/dist/debug/error-stack.js +52 -0
  24. package/dist/internal/document.d.ts +12 -0
  25. package/dist/internal/document.d.ts.map +1 -0
  26. package/dist/internal/document.jsx +56 -0
  27. package/dist/internal/entry-client.d.ts +2 -0
  28. package/dist/internal/entry-client.d.ts.map +1 -0
  29. package/dist/internal/entry-client.jsx +8 -0
  30. package/dist/internal/entry-server.d.ts +14 -0
  31. package/dist/internal/entry-server.d.ts.map +1 -0
  32. package/dist/internal/entry-server.jsx +71 -0
  33. package/dist/runtime/create-route.d.ts +8 -0
  34. package/dist/runtime/create-route.d.ts.map +1 -0
  35. package/dist/runtime/create-route.js +18 -0
  36. package/dist/runtime/head.d.ts +10 -0
  37. package/dist/runtime/head.d.ts.map +1 -0
  38. package/dist/runtime/head.js +111 -0
  39. package/dist/runtime/index.d.ts +6 -0
  40. package/dist/runtime/index.d.ts.map +1 -0
  41. package/dist/runtime/index.jsx +4 -0
  42. package/dist/runtime/navigation.d.ts +36 -0
  43. package/dist/runtime/navigation.d.ts.map +1 -0
  44. package/dist/runtime/navigation.js +70 -0
  45. package/dist/runtime/path.d.ts +11 -0
  46. package/dist/runtime/path.d.ts.map +1 -0
  47. package/dist/runtime/path.js +80 -0
  48. package/dist/runtime/resolve.d.ts +11 -0
  49. package/dist/runtime/resolve.d.ts.map +1 -0
  50. package/dist/runtime/resolve.js +449 -0
  51. package/dist/runtime/runtime.d.ts +40 -0
  52. package/dist/runtime/runtime.d.ts.map +1 -0
  53. package/dist/runtime/runtime.jsx +779 -0
  54. package/dist/runtime/search.d.ts +23 -0
  55. package/dist/runtime/search.d.ts.map +1 -0
  56. package/dist/runtime/search.js +178 -0
  57. package/dist/runtime/server.d.ts +10 -0
  58. package/dist/runtime/server.d.ts.map +1 -0
  59. package/dist/runtime/server.js +5 -0
  60. package/dist/runtime/types.d.ts +248 -0
  61. package/dist/runtime/types.d.ts.map +1 -0
  62. package/dist/runtime/types.js +1 -0
  63. package/dist/seo.d.ts +16 -0
  64. package/dist/seo.d.ts.map +1 -0
  65. package/dist/seo.js +69 -0
  66. package/dist/server/index.d.ts +53 -0
  67. package/dist/server/index.d.ts.map +1 -0
  68. package/dist/server/index.js +268 -0
  69. package/dist/session.d.ts +23 -0
  70. package/dist/session.d.ts.map +1 -0
  71. package/dist/session.js +58 -0
  72. package/dist/vite/index.d.ts +13 -0
  73. package/dist/vite/index.d.ts.map +1 -0
  74. package/dist/vite/index.js +174 -0
  75. package/dist/vite/routes-plugin.d.ts +4 -0
  76. package/dist/vite/routes-plugin.d.ts.map +1 -0
  77. package/dist/vite/routes-plugin.js +345 -0
  78. package/dist/vite/server-only.d.ts +3 -0
  79. package/dist/vite/server-only.d.ts.map +1 -0
  80. package/dist/vite/server-only.js +342 -0
  81. package/package.json +76 -0
  82. package/templates/bare/README.md +22 -0
  83. package/templates/bare/src/components/counter-card.tsx +19 -0
  84. package/templates/bare/src/routes/__root.tsx +24 -0
  85. package/templates/bare/src/routes/index.tsx +36 -0
  86. package/templates/base/gitignore +4 -0
  87. package/templates/base/public/favicon.svg +5 -0
  88. package/templates/base/tsconfig.json +18 -0
  89. package/templates/modern/README.md +28 -0
  90. package/templates/modern/src/components/counter-card.tsx +19 -0
  91. package/templates/modern/src/routes/__root.tsx +42 -0
  92. package/templates/modern/src/routes/about.tsx +55 -0
  93. package/templates/modern/src/routes/index.tsx +51 -0
  94. package/templates/styling/css/styles.css +269 -0
  95. package/templates/styling/css-modules/styles.css +269 -0
  96. package/templates/styling/tailwind/styles.css +271 -0
@@ -0,0 +1,779 @@
1
+ import { createContext, createEffect, createSignal, ErrorBoundary, onCleanup, sharedConfig, splitProps, useContext } from 'solid-js';
2
+ import { hydrate } from 'solid-js/web';
3
+ import { formatParsedStack, parseErrorStack } from '../debug/error-stack';
4
+ import { syncHead } from './head';
5
+ import { normalizeHref } from './path';
6
+ import { materializeBootstrapPayload, materializePayloadEnvelope, } from './resolve';
7
+ import { parseSearchString, resolveMergedSearch, resolveNavigateHref } from './search';
8
+ const PREFETCH_CACHE_MAX_ENTRIES = 24;
9
+ const PREFETCH_CACHE_TTL_MS = 30_000;
10
+ const isDev = import.meta.env.DEV;
11
+ const RouterContext = createContext();
12
+ const OutletContext = createContext();
13
+ const MatchContext = createContext();
14
+ const ISLAND_ROOT_ATTRIBUTE = 'data-vrz-island-root';
15
+ const ISLAND_CONTEXT_ATTRIBUTE = 'data-vrz-hctx';
16
+ const RETRY_ATTRIBUTE = 'data-vrz-retry';
17
+ let _activeRouter;
18
+ function formatMatchChain(matches) {
19
+ return matches.map((match) => `${match.id}:${match.hydration}`).join(' > ');
20
+ }
21
+ function logDevRouterEvent(label, details) {
22
+ if (!isDev || typeof window === 'undefined') {
23
+ return;
24
+ }
25
+ console.info(`[VorzelaJs][router] ${label}`, details);
26
+ }
27
+ function logDevRouterError(scope, error, meta = {}) {
28
+ if (!isDev || typeof window === 'undefined') {
29
+ return;
30
+ }
31
+ const parsedStack = parseErrorStack(error);
32
+ console.groupCollapsed(`[VorzelaJs][router:${scope}] ${error instanceof Error ? error.message : 'Unexpected error'}`);
33
+ console.info(meta);
34
+ if (parsedStack) {
35
+ const lines = formatParsedStack(parsedStack);
36
+ if (lines.length > 0) {
37
+ console.info(lines.join('\n'));
38
+ }
39
+ }
40
+ console.error(error);
41
+ console.groupEnd();
42
+ }
43
+ function prunePrefetchCache(prefetchCache) {
44
+ const now = Date.now();
45
+ for (const [href, entry] of prefetchCache) {
46
+ if (now - entry.createdAt > PREFETCH_CACHE_TTL_MS) {
47
+ prefetchCache.delete(href);
48
+ }
49
+ }
50
+ while (prefetchCache.size > PREFETCH_CACHE_MAX_ENTRIES) {
51
+ const oldestKey = prefetchCache.keys().next().value;
52
+ if (!oldestKey) {
53
+ break;
54
+ }
55
+ prefetchCache.delete(oldestKey);
56
+ }
57
+ }
58
+ function commitState(nextState, setState, options = {}) {
59
+ setState(nextState);
60
+ syncHead(nextState.head);
61
+ if (typeof window === 'undefined')
62
+ return;
63
+ const href = `${nextState.pathname}${nextState.search}`;
64
+ if (options.replace) {
65
+ window.history.replaceState({}, '', href);
66
+ }
67
+ else if (!options.force) {
68
+ window.history.pushState({}, '', href);
69
+ }
70
+ if (options.scroll !== false) {
71
+ window.scrollTo({ left: 0, top: 0 });
72
+ }
73
+ }
74
+ function DefaultNotFoundComponent() {
75
+ return (<div class="page-card page-card--centered">
76
+ <p class="eyebrow">404</p>
77
+ <h1>Page not found</h1>
78
+ <p class="lead-copy">The requested route could not be resolved.</p>
79
+ </div>);
80
+ }
81
+ function DefaultRouteErrorComponent(props) {
82
+ const debugLines = () => formatParsedStack(props.error.debug);
83
+ return (<section class="page-card page-card--centered">
84
+ <p class="eyebrow">{props.error.status}</p>
85
+ <h1>Route failed</h1>
86
+ <p class="lead-copy">{props.error.message}</p>
87
+ <p class="mono-note">phase: {props.error.phase}</p>
88
+ {isDev && debugLines().length > 0 && (<details class="mono-note" open>
89
+ <summary>Debug stack</summary>
90
+ <pre>{debugLines().join('\n')}</pre>
91
+ </details>)}
92
+ <button type="button" class="button button--secondary" data-vrz-retry="">
93
+ Try again
94
+ </button>
95
+ </section>);
96
+ }
97
+ function createRenderError(error) {
98
+ const debug = isDev ? parseErrorStack(error) : undefined;
99
+ if (error instanceof Error) {
100
+ return {
101
+ debug,
102
+ message: error.message || 'Unexpected render error',
103
+ name: error.name || 'Error',
104
+ phase: 'render',
105
+ status: 500,
106
+ };
107
+ }
108
+ return {
109
+ debug,
110
+ message: typeof error === 'string' ? error : 'Unexpected render error',
111
+ name: 'Error',
112
+ phase: 'render',
113
+ status: 500,
114
+ };
115
+ }
116
+ function RouteErrorView(props) {
117
+ const ErrorComponent = props.errorComponent;
118
+ return ErrorComponent
119
+ ? <ErrorComponent error={props.error} reset={props.reset}/>
120
+ : <DefaultRouteErrorComponent error={props.error} reset={props.reset}/>;
121
+ }
122
+ function createAfterLoadLocation(pathname, search) {
123
+ if (typeof window === 'undefined') {
124
+ const url = new URL(`http://localhost${pathname}${search}`);
125
+ return {
126
+ href: url.href,
127
+ pathname,
128
+ search,
129
+ searchParams: url.searchParams,
130
+ };
131
+ }
132
+ const url = new URL(`${pathname}${search}`, window.location.origin);
133
+ return {
134
+ href: url.href,
135
+ pathname,
136
+ search,
137
+ searchParams: url.searchParams,
138
+ };
139
+ }
140
+ function isHydrationBoundary(state, index) {
141
+ const currentMatch = state.matches[index];
142
+ const previousMatch = state.matches[index - 1];
143
+ return currentMatch?.hydration === 'client' && previousMatch?.hydration !== 'client';
144
+ }
145
+ function canHydrateBoundary(state, startIndex) {
146
+ const subtreeIds = new Set(state.matches.slice(startIndex).map((match) => match.id));
147
+ if (state.notFound
148
+ && subtreeIds.has(state.notFound.targetId)
149
+ && state.notFound.handlerId
150
+ && !subtreeIds.has(state.notFound.handlerId)) {
151
+ return false;
152
+ }
153
+ if (state.routeError
154
+ && subtreeIds.has(state.routeError.targetId)
155
+ && state.routeError.handlerId
156
+ && !subtreeIds.has(state.routeError.handlerId)) {
157
+ return false;
158
+ }
159
+ return true;
160
+ }
161
+ function getHydrationRoots(state) {
162
+ return state.matches.flatMap((match, index) => (isHydrationBoundary(state, index) && canHydrateBoundary(state, index)
163
+ ? [{ id: match.id, index }]
164
+ : []));
165
+ }
166
+ function createHydrationSubtreeState(state, startIndex) {
167
+ const matches = state.matches.slice(startIndex);
168
+ const subtreeIds = new Set(matches.map((match) => match.id));
169
+ return {
170
+ matches,
171
+ notFound: state.notFound && subtreeIds.has(state.notFound.targetId)
172
+ && (!state.notFound.handlerId || subtreeIds.has(state.notFound.handlerId))
173
+ ? state.notFound
174
+ : undefined,
175
+ payloadHtml: undefined,
176
+ renderSource: 'component',
177
+ routeError: state.routeError && subtreeIds.has(state.routeError.targetId)
178
+ && (!state.routeError.handlerId || subtreeIds.has(state.routeError.handlerId))
179
+ ? state.routeError
180
+ : undefined,
181
+ };
182
+ }
183
+ function replaceAppHtml(html) {
184
+ const app = document.getElementById('app');
185
+ if (!(app instanceof HTMLElement)) {
186
+ throw new Error('Missing VorzelaJs app root');
187
+ }
188
+ app.innerHTML = html;
189
+ return app;
190
+ }
191
+ function readIslandHydrationContext(mount) {
192
+ const raw = mount.getAttribute(ISLAND_CONTEXT_ATTRIBUTE);
193
+ if (!raw) {
194
+ return undefined;
195
+ }
196
+ const sep = raw.indexOf(':');
197
+ if (sep < 0) {
198
+ return undefined;
199
+ }
200
+ return {
201
+ id: raw.slice(0, sep),
202
+ count: Number(raw.slice(sep + 1)),
203
+ };
204
+ }
205
+ function isRouteRedirectData(value) {
206
+ return typeof value === 'object'
207
+ && value !== null
208
+ && 'to' in value
209
+ && 'replace' in value
210
+ && 'status' in value;
211
+ }
212
+ function isRouteRedirectEnvelope(value) {
213
+ return typeof value === 'object'
214
+ && value !== null
215
+ && 'redirect' in value
216
+ && isRouteRedirectData(value.redirect);
217
+ }
218
+ function reportAfterLoadError(match, error) {
219
+ logDevRouterError('afterLoad', error, { routeId: match.id });
220
+ console.error(`[VorzelaJs] afterLoad failed for route ${match.id}`, error);
221
+ }
222
+ function runAfterLoadHooks(router, state) {
223
+ if (typeof window === 'undefined' || state.matches.length === 0 || state.notFound || state.routeError) {
224
+ return;
225
+ }
226
+ const location = createAfterLoadLocation(state.pathname, state.search);
227
+ for (const match of state.matches) {
228
+ const afterLoad = match.route.options.afterLoad;
229
+ if (!afterLoad) {
230
+ continue;
231
+ }
232
+ const afterLoadContext = {
233
+ context: router.context,
234
+ loaderData: match.loaderData,
235
+ location,
236
+ params: match.params,
237
+ pathname: state.pathname,
238
+ search: match.search,
239
+ };
240
+ try {
241
+ const result = afterLoad(afterLoadContext);
242
+ void Promise.resolve(result).catch((error) => {
243
+ reportAfterLoadError(match, error);
244
+ });
245
+ }
246
+ catch (error) {
247
+ reportAfterLoadError(match, error);
248
+ }
249
+ }
250
+ }
251
+ export function createRouter(initialPayload, options = {}) {
252
+ const [state, setState] = createSignal({
253
+ head: initialPayload.head,
254
+ matches: [],
255
+ notFound: initialPayload.notFound,
256
+ pathname: initialPayload.pathname,
257
+ renderSource: 'component',
258
+ routeError: initialPayload.routeError,
259
+ search: initialPayload.search,
260
+ });
261
+ const context = options.context ?? {};
262
+ let initialized = false;
263
+ let afterLoadFrame;
264
+ let afterLoadToken = 0;
265
+ const islandDisposers = [];
266
+ const prefetchCache = new Map();
267
+ const resolveTargetHref = (to) => {
268
+ const currentState = state();
269
+ return typeof to === 'string'
270
+ ? normalizeHref(to)
271
+ : resolveNavigateHref(`${currentState.pathname}${currentState.search}`, to, parseSearchString(currentState.search));
272
+ };
273
+ const getCachedNavigationState = (href) => {
274
+ prunePrefetchCache(prefetchCache);
275
+ return prefetchCache.get(href)?.promise;
276
+ };
277
+ const storeNavigationState = (href, promise) => {
278
+ prefetchCache.set(href, {
279
+ createdAt: Date.now(),
280
+ promise,
281
+ });
282
+ prunePrefetchCache(prefetchCache);
283
+ return promise;
284
+ };
285
+ const disposeHydrationRoots = () => {
286
+ for (const dispose of islandDisposers.splice(0)) {
287
+ dispose();
288
+ }
289
+ };
290
+ const scheduleAfterLoad = (nextState) => {
291
+ if (typeof window === 'undefined') {
292
+ return;
293
+ }
294
+ const token = ++afterLoadToken;
295
+ if (afterLoadFrame !== undefined) {
296
+ window.cancelAnimationFrame(afterLoadFrame);
297
+ }
298
+ afterLoadFrame = window.requestAnimationFrame(() => {
299
+ afterLoadFrame = undefined;
300
+ if (token !== afterLoadToken) {
301
+ return;
302
+ }
303
+ runAfterLoadHooks(router, nextState);
304
+ });
305
+ };
306
+ const hydrateIslands = (nextState) => {
307
+ if (typeof window === 'undefined') {
308
+ return;
309
+ }
310
+ const app = document.getElementById('app');
311
+ if (!(app instanceof HTMLElement)) {
312
+ throw new Error('Missing VorzelaJs app root');
313
+ }
314
+ const mountPoints = Array.from(app.querySelectorAll(`[${ISLAND_ROOT_ATTRIBUTE}]`));
315
+ const hydrationRoots = getHydrationRoots(nextState);
316
+ for (const root of hydrationRoots) {
317
+ const mount = mountPoints.find((candidate) => candidate.getAttribute(ISLAND_ROOT_ATTRIBUTE) === root.id);
318
+ if (!mount) {
319
+ continue;
320
+ }
321
+ const subtreeState = createHydrationSubtreeState(nextState, root.index);
322
+ const retry = () => {
323
+ void router.navigate(`${router.state().pathname}${router.state().search}`, {
324
+ force: true,
325
+ replace: true,
326
+ scroll: false,
327
+ });
328
+ };
329
+ const hctx = readIslandHydrationContext(mount);
330
+ islandDisposers.push(hydrate(() => {
331
+ if (hctx && sharedConfig.context) {
332
+ sharedConfig.context.count = hctx.count;
333
+ }
334
+ return renderResolvedMatches(subtreeState, { retry });
335
+ }, mount, {
336
+ renderId: hctx?.id,
337
+ }));
338
+ }
339
+ logDevRouterEvent('hydrate', {
340
+ islands: hydrationRoots.map((root) => root.id),
341
+ matches: formatMatchChain(nextState.matches),
342
+ pathname: `${nextState.pathname}${nextState.search}`,
343
+ });
344
+ };
345
+ const mountState = (nextState, navigationOptions = {}) => {
346
+ disposeHydrationRoots();
347
+ if (typeof window !== 'undefined' && nextState.payloadHtml) {
348
+ replaceAppHtml(nextState.payloadHtml);
349
+ }
350
+ commitState(nextState, setState, navigationOptions);
351
+ hydrateIslands(nextState);
352
+ scheduleAfterLoad(nextState);
353
+ if (nextState.payloadHtml) {
354
+ storeNavigationState(`${nextState.pathname}${nextState.search}`, Promise.resolve(nextState));
355
+ }
356
+ logDevRouterEvent('commit', {
357
+ matches: formatMatchChain(nextState.matches),
358
+ pathname: `${nextState.pathname}${nextState.search}`,
359
+ renderSource: nextState.renderSource,
360
+ routeError: nextState.routeError?.error.message,
361
+ });
362
+ };
363
+ const fetchNavigationState = async (href) => {
364
+ const response = await fetch(`/__vorzela/payload?path=${encodeURIComponent(href)}`, {
365
+ credentials: 'same-origin',
366
+ headers: {
367
+ 'X-Vorzela-Navigation': 'payload',
368
+ },
369
+ redirect: 'manual',
370
+ });
371
+ let payload;
372
+ try {
373
+ payload = await response.json();
374
+ }
375
+ catch (error) {
376
+ throw new Error(`Failed to parse route payload for ${href}: ${error.message}`);
377
+ }
378
+ if (isRouteRedirectEnvelope(payload)) {
379
+ return payload.redirect;
380
+ }
381
+ if (typeof payload !== 'object'
382
+ || payload === null
383
+ || !('html' in payload)
384
+ || !('matches' in payload)) {
385
+ throw new Error(`Unexpected route payload for ${href}`);
386
+ }
387
+ return materializePayloadEnvelope(payload);
388
+ };
389
+ const requestNavigationState = (href, reason) => {
390
+ const cached = getCachedNavigationState(href);
391
+ if (cached) {
392
+ logDevRouterEvent(`${reason}:cache-hit`, { href });
393
+ return cached;
394
+ }
395
+ logDevRouterEvent(`${reason}:network`, { href });
396
+ const pending = fetchNavigationState(href).catch((error) => {
397
+ prefetchCache.delete(href);
398
+ throw error;
399
+ });
400
+ return storeNavigationState(href, pending);
401
+ };
402
+ const prefetch = async (to) => {
403
+ if (typeof window === 'undefined') {
404
+ return;
405
+ }
406
+ const href = resolveTargetHref(to);
407
+ if (href === `${state().pathname}${state().search}`) {
408
+ return;
409
+ }
410
+ try {
411
+ await requestNavigationState(href, 'prefetch');
412
+ }
413
+ catch (error) {
414
+ logDevRouterError('prefetch', error, { href });
415
+ }
416
+ };
417
+ const navigate = async (to, options = {}) => {
418
+ const currentState = state();
419
+ const href = resolveTargetHref(to);
420
+ const replace = typeof to === 'string' ? options.replace : to.replace ?? options.replace;
421
+ const scroll = typeof to === 'string' ? options.scroll : to.scroll ?? options.scroll;
422
+ if (!options.force && href === `${currentState.pathname}${currentState.search}`) {
423
+ return;
424
+ }
425
+ let nextState;
426
+ try {
427
+ nextState = await requestNavigationState(href, 'navigate');
428
+ }
429
+ catch (error) {
430
+ logDevRouterError('navigate', error, { href });
431
+ throw error;
432
+ }
433
+ if (isRouteRedirectData(nextState)) {
434
+ await navigate(nextState.to, {
435
+ force: true,
436
+ replace: nextState.replace,
437
+ scroll,
438
+ });
439
+ return;
440
+ }
441
+ mountState(nextState, {
442
+ ...options,
443
+ replace,
444
+ scroll,
445
+ });
446
+ };
447
+ const setSearch = (search, options = {}) => {
448
+ const currentState = state();
449
+ const nextSearch = resolveMergedSearch(parseSearchString(currentState.search), parseSearchString(currentState.search), search);
450
+ return navigate({
451
+ replace: options.replace,
452
+ scroll: options.scroll,
453
+ search: nextSearch,
454
+ to: currentState.pathname,
455
+ });
456
+ };
457
+ const init = async () => {
458
+ const nextState = await materializeBootstrapPayload(initialPayload);
459
+ mountState(nextState, { force: true, replace: true, scroll: false });
460
+ logDevRouterEvent('init', {
461
+ matches: formatMatchChain(nextState.matches),
462
+ pathname: `${nextState.pathname}${nextState.search}`,
463
+ });
464
+ if (!initialized && typeof window !== 'undefined') {
465
+ window.addEventListener('popstate', () => {
466
+ void navigate(`${window.location.pathname}${window.location.search}`, {
467
+ force: true,
468
+ replace: true,
469
+ scroll: false,
470
+ });
471
+ });
472
+ document.addEventListener('click', (event) => {
473
+ if (event.defaultPrevented
474
+ || event.button !== 0
475
+ || event.metaKey
476
+ || event.altKey
477
+ || event.ctrlKey
478
+ || event.shiftKey) {
479
+ return;
480
+ }
481
+ const target = event.target;
482
+ if (!(target instanceof Element)) {
483
+ return;
484
+ }
485
+ const retryButton = target.closest(`[${RETRY_ATTRIBUTE}]`);
486
+ if (retryButton instanceof HTMLButtonElement) {
487
+ event.preventDefault();
488
+ void navigate(`${window.location.pathname}${window.location.search}`, {
489
+ force: true,
490
+ replace: true,
491
+ scroll: false,
492
+ });
493
+ return;
494
+ }
495
+ const anchor = target.closest('a[href]');
496
+ if (!(anchor instanceof HTMLAnchorElement)) {
497
+ return;
498
+ }
499
+ if (anchor.target === '_blank' || anchor.hasAttribute('download')) {
500
+ return;
501
+ }
502
+ const nextUrl = new URL(anchor.href, window.location.origin);
503
+ if (nextUrl.origin !== window.location.origin) {
504
+ return;
505
+ }
506
+ if (nextUrl.hash
507
+ && nextUrl.pathname === window.location.pathname
508
+ && nextUrl.search === window.location.search) {
509
+ return;
510
+ }
511
+ event.preventDefault();
512
+ void navigate(`${nextUrl.pathname}${nextUrl.search}`, {
513
+ replace: anchor.hasAttribute('data-vrz-replace'),
514
+ });
515
+ });
516
+ initialized = true;
517
+ }
518
+ };
519
+ const router = {
520
+ context,
521
+ init,
522
+ navigate,
523
+ prefetch,
524
+ setSearch,
525
+ state,
526
+ };
527
+ _activeRouter = router;
528
+ return router;
529
+ }
530
+ function PayloadOutlet(props) {
531
+ return <div class="payload-fragment" innerHTML={props.html}/>;
532
+ }
533
+ export function renderResolvedMatches(state, options = {}) {
534
+ const renderAt = (index) => {
535
+ const currentMatch = state.matches[index];
536
+ const isIsland = options.wrapHydrationBoundaries && isHydrationBoundary(state, index);
537
+ const captureIslandContext = () => isIsland && sharedConfig.context
538
+ ? `${sharedConfig.context.id}:${sharedConfig.context.count}`
539
+ : undefined;
540
+ const notFoundHandlerMatch = state.notFound?.handlerId
541
+ ? state.matches.find((match) => match.id === state.notFound?.handlerId)
542
+ : undefined;
543
+ const routeErrorHandlerMatch = state.routeError?.handlerId
544
+ ? state.matches.find((match) => match.id === state.routeError?.handlerId)
545
+ : undefined;
546
+ if (state.notFound && currentMatch.id === state.notFound.targetId) {
547
+ const NotFoundComponent = notFoundHandlerMatch?.route.options.notFoundComponent
548
+ ?? DefaultNotFoundComponent;
549
+ const islandContext = captureIslandContext();
550
+ const content = (<MatchContext.Provider value={currentMatch}>
551
+ <NotFoundComponent />
552
+ </MatchContext.Provider>);
553
+ return isIsland
554
+ ? <div data-vrz-island-root={currentMatch.id} data-vrz-hctx={islandContext}>{content}</div>
555
+ : content;
556
+ }
557
+ if (state.routeError && currentMatch.id === state.routeError.targetId) {
558
+ const errorComponent = routeErrorHandlerMatch?.route.options.errorComponent;
559
+ const islandContext = captureIslandContext();
560
+ const content = (<MatchContext.Provider value={currentMatch}>
561
+ <RouteErrorView error={state.routeError.error} errorComponent={errorComponent} reset={options.retry ?? (() => undefined)}/>
562
+ </MatchContext.Provider>);
563
+ return isIsland
564
+ ? <div data-vrz-island-root={currentMatch.id} data-vrz-hctx={islandContext}>{content}</div>
565
+ : content;
566
+ }
567
+ if (index === state.matches.length - 1
568
+ && state.renderSource === 'payload'
569
+ && state.payloadHtml) {
570
+ const islandContext = captureIslandContext();
571
+ const content = <PayloadOutlet html={state.payloadHtml}/>;
572
+ return isIsland
573
+ ? <div data-vrz-island-root={currentMatch.id} data-vrz-hctx={islandContext}>{content}</div>
574
+ : content;
575
+ }
576
+ const RouteComponent = currentMatch.route.options.component;
577
+ const errorComponent = currentMatch.route.options.errorComponent;
578
+ const outlet = index === state.matches.length - 1 ? null : renderAt(index + 1);
579
+ const islandContext = captureIslandContext();
580
+ const content = (<MatchContext.Provider value={currentMatch}>
581
+ <OutletContext.Provider value={outlet ?? undefined}>
582
+ <ErrorBoundary fallback={(error, reset) => {
583
+ const routeError = createRenderError(error);
584
+ logDevRouterError('render', error, {
585
+ routeId: currentMatch.id,
586
+ });
587
+ options.onRenderError?.(routeError);
588
+ return (<RouteErrorView error={routeError} errorComponent={errorComponent} reset={reset}/>);
589
+ }}>
590
+ <RouteComponent loaderData={currentMatch.loaderData} params={currentMatch.params} search={currentMatch.search}>
591
+ {outlet}
592
+ </RouteComponent>
593
+ </ErrorBoundary>
594
+ </OutletContext.Provider>
595
+ </MatchContext.Provider>);
596
+ return isIsland
597
+ ? <div data-vrz-island-root={currentMatch.id} data-vrz-hctx={islandContext}>{content}</div>
598
+ : content;
599
+ };
600
+ if (state.matches.length === 0) {
601
+ return <div class="route-loading">Loading route...</div>;
602
+ }
603
+ return renderAt(0);
604
+ }
605
+ export function RouterProvider(props) {
606
+ let afterLoadFrame;
607
+ let afterLoadToken = 0;
608
+ let currentRenderFailed = false;
609
+ createEffect(() => {
610
+ const currentState = props.router.state();
611
+ if (typeof window === 'undefined') {
612
+ return;
613
+ }
614
+ currentRenderFailed = false;
615
+ const currentToken = ++afterLoadToken;
616
+ if (afterLoadFrame !== undefined) {
617
+ window.cancelAnimationFrame(afterLoadFrame);
618
+ }
619
+ afterLoadFrame = window.requestAnimationFrame(() => {
620
+ afterLoadFrame = undefined;
621
+ if (currentToken !== afterLoadToken) {
622
+ return;
623
+ }
624
+ if (currentRenderFailed) {
625
+ return;
626
+ }
627
+ runAfterLoadHooks(props.router, currentState);
628
+ });
629
+ });
630
+ onCleanup(() => {
631
+ if (typeof window !== 'undefined' && afterLoadFrame !== undefined) {
632
+ window.cancelAnimationFrame(afterLoadFrame);
633
+ }
634
+ });
635
+ const retry = () => {
636
+ void props.router.navigate(`${props.router.state().pathname}${props.router.state().search}`, {
637
+ force: true,
638
+ replace: true,
639
+ scroll: false,
640
+ });
641
+ };
642
+ return (<RouterContext.Provider value={props.router}>
643
+ {renderResolvedMatches(props.router.state(), {
644
+ onRenderError: () => {
645
+ currentRenderFailed = true;
646
+ },
647
+ retry,
648
+ })}
649
+ </RouterContext.Provider>);
650
+ }
651
+ export function Outlet() {
652
+ return useContext(OutletContext) ?? null;
653
+ }
654
+ export function useRouter() {
655
+ const router = useContext(RouterContext) ?? _activeRouter;
656
+ if (!router) {
657
+ throw new Error('useRouter must be used inside a <RouterProvider>');
658
+ }
659
+ return router;
660
+ }
661
+ export function useNavigate() {
662
+ const router = useContext(RouterContext) ?? _activeRouter;
663
+ return (to, options) => {
664
+ if (!router) {
665
+ return Promise.reject(new Error('useNavigate cannot be called during SSR'));
666
+ }
667
+ return router.navigate(to, options);
668
+ };
669
+ }
670
+ export function useParams() {
671
+ const match = useContext(MatchContext);
672
+ if (!match) {
673
+ throw new Error('useParams must be used inside a route component');
674
+ }
675
+ return match.params;
676
+ }
677
+ export function useLoaderData() {
678
+ const match = useContext(MatchContext);
679
+ if (!match) {
680
+ throw new Error('useLoaderData must be used inside a route component');
681
+ }
682
+ return match.loaderData;
683
+ }
684
+ export function useSearch() {
685
+ const match = useContext(MatchContext);
686
+ const router = useContext(RouterContext) ?? _activeRouter;
687
+ if (!match) {
688
+ throw new Error('useSearch must be used inside a route component');
689
+ }
690
+ return () => {
691
+ if (!router) {
692
+ return match.search;
693
+ }
694
+ const currentMatch = router.state().matches.find((candidate) => candidate.id === match.id);
695
+ return (currentMatch?.search ?? match.search);
696
+ };
697
+ }
698
+ export function useSetSearch() {
699
+ const router = useContext(RouterContext) ?? _activeRouter;
700
+ const search = useSearch();
701
+ return (nextSearch, options = {}) => {
702
+ if (!router) {
703
+ return Promise.reject(new Error('useSetSearch cannot be called during SSR'));
704
+ }
705
+ const currentState = router.state();
706
+ const mergedSearch = resolveMergedSearch(parseSearchString(currentState.search), search(), nextSearch);
707
+ return router.navigate({
708
+ replace: options.replace,
709
+ scroll: options.scroll,
710
+ search: mergedSearch,
711
+ to: currentState.pathname,
712
+ });
713
+ };
714
+ }
715
+ export function readBootstrapPayload() {
716
+ const element = document.getElementById('__VORZELA_DATA__');
717
+ if (!(element instanceof HTMLScriptElement) || !element.textContent) {
718
+ throw new Error('Missing VorzelaJs bootstrap payload');
719
+ }
720
+ return JSON.parse(element.textContent);
721
+ }
722
+ export function Link(props) {
723
+ const router = useContext(RouterContext) ?? _activeRouter;
724
+ const [local, rest] = splitProps(props, ['children', 'onClick', 'onFocus', 'onMouseEnter', 'onTouchStart', 'replace', 'to']);
725
+ const onClick = local.onClick;
726
+ const onFocus = local.onFocus;
727
+ const onMouseEnter = local.onMouseEnter;
728
+ const onTouchStart = local.onTouchStart;
729
+ const prefetchLink = () => {
730
+ if (!router) {
731
+ return;
732
+ }
733
+ const nextUrl = new URL(local.to, window.location.origin);
734
+ if (nextUrl.origin !== window.location.origin) {
735
+ return;
736
+ }
737
+ void router.prefetch(`${nextUrl.pathname}${nextUrl.search}`);
738
+ };
739
+ const handleClick = (event) => {
740
+ onClick?.(event);
741
+ if (event.defaultPrevented
742
+ || event.button !== 0
743
+ || event.metaKey
744
+ || event.altKey
745
+ || event.ctrlKey
746
+ || event.shiftKey
747
+ || rest.target === '_blank'
748
+ || !router) {
749
+ return;
750
+ }
751
+ const nextUrl = new URL(local.to, window.location.origin);
752
+ if (nextUrl.origin !== window.location.origin) {
753
+ return;
754
+ }
755
+ event.preventDefault();
756
+ void router.navigate(`${nextUrl.pathname}${nextUrl.search}`, { replace: local.replace });
757
+ };
758
+ const handleMouseEnter = (event) => {
759
+ onMouseEnter?.(event);
760
+ if (!event.defaultPrevented) {
761
+ prefetchLink();
762
+ }
763
+ };
764
+ const handleFocus = (event) => {
765
+ onFocus?.(event);
766
+ if (!event.defaultPrevented) {
767
+ prefetchLink();
768
+ }
769
+ };
770
+ const handleTouchStart = (event) => {
771
+ onTouchStart?.(event);
772
+ if (!event.defaultPrevented) {
773
+ prefetchLink();
774
+ }
775
+ };
776
+ return (<a {...rest} data-vrz-replace={local.replace ? '' : undefined} href={local.to} onClick={handleClick} onFocus={handleFocus} onMouseEnter={handleMouseEnter} onTouchStart={handleTouchStart}>
777
+ {local.children}
778
+ </a>);
779
+ }