round-core 0.0.7 → 0.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 +62 -41
- package/dist/index.d.ts +341 -341
- package/dist/index.js +211 -192
- package/dist/vite-plugin.js +52 -3
- package/package.json +7 -4
- package/.github/workflows/benchmarks.yml +0 -44
- package/Round.png +0 -0
- package/benchmarks/apps/react/index.html +0 -9
- package/benchmarks/apps/react/main.jsx +0 -25
- package/benchmarks/apps/react/vite.config.js +0 -12
- package/benchmarks/apps/round/index.html +0 -11
- package/benchmarks/apps/round/main.jsx +0 -22
- package/benchmarks/apps/round/vite.config.js +0 -15
- package/benchmarks/bun.lock +0 -497
- package/benchmarks/dist-bench/react/assets/index-9KGqIPOU.js +0 -8
- package/benchmarks/dist-bench/react/index.html +0 -10
- package/benchmarks/dist-bench/round/assets/index-CBBIRhox.js +0 -52
- package/benchmarks/dist-bench/round/index.html +0 -8
- package/benchmarks/package.json +0 -22
- package/benchmarks/scripts/measure-build.js +0 -64
- package/benchmarks/tests/runtime.bench.js +0 -51
- package/benchmarks/vitest.config.js +0 -8
- package/bun.lock +0 -425
- package/cli.js +0 -2
- package/extension/.vscodeignore +0 -5
- package/extension/LICENSE +0 -21
- package/extension/cgmanifest.json +0 -45
- package/extension/extension.js +0 -163
- package/extension/images/round-config-dark.svg +0 -10
- package/extension/images/round-config-light.svg +0 -10
- package/extension/images/round-dark.svg +0 -10
- package/extension/images/round-light.svg +0 -10
- package/extension/javascript-language-configuration.json +0 -241
- package/extension/package-lock.json +0 -97
- package/extension/package.json +0 -119
- package/extension/package.nls.json +0 -4
- package/extension/round-0.1.0.vsix +0 -0
- package/extension/round-lsp/package-lock.json +0 -185
- package/extension/round-lsp/package.json +0 -21
- package/extension/round-lsp/src/round-transformer-lsp.js +0 -248
- package/extension/round-lsp/src/server.js +0 -396
- package/extension/snippets/javascript.code-snippets +0 -266
- package/extension/snippets/round.code-snippets +0 -109
- package/extension/syntaxes/JavaScript.tmLanguage.json +0 -6001
- package/extension/syntaxes/JavaScriptReact.tmLanguage.json +0 -6066
- package/extension/syntaxes/Readme.md +0 -12
- package/extension/syntaxes/Regular Expressions (JavaScript).tmLanguage +0 -237
- package/extension/syntaxes/Round.tmLanguage.json +0 -290
- package/extension/syntaxes/RoundInject.tmLanguage.json +0 -20
- package/extension/tags-language-configuration.json +0 -152
- package/extension/temp_astro/package-lock.json +0 -912
- package/extension/temp_astro/package.json +0 -16
- package/extension/types/round-core.d.ts +0 -326
- package/index.js +0 -2
- package/logo.svg +0 -10
- package/src/cli.js +0 -608
- package/src/compiler/index.js +0 -2
- package/src/compiler/transformer.js +0 -443
- package/src/compiler/vite-plugin.js +0 -472
- package/src/index.d.ts +0 -341
- package/src/index.js +0 -45
- package/src/runtime/context.js +0 -101
- package/src/runtime/dom.js +0 -403
- package/src/runtime/error-boundary.js +0 -48
- package/src/runtime/error-reporter.js +0 -13
- package/src/runtime/error-store.js +0 -85
- package/src/runtime/errors.js +0 -152
- package/src/runtime/lifecycle.js +0 -142
- package/src/runtime/markdown.js +0 -72
- package/src/runtime/router.js +0 -468
- package/src/runtime/signals.js +0 -548
- package/src/runtime/store.js +0 -215
- package/src/runtime/suspense.js +0 -128
- package/vite.config.build.js +0 -48
- package/vite.config.js +0 -10
- package/vitest.config.js +0 -8
package/src/runtime/router.js
DELETED
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
import { signal, effect } from './signals.js';
|
|
2
|
-
import { createElement } from './dom.js';
|
|
3
|
-
import { createContext, readContext } from './context.js';
|
|
4
|
-
|
|
5
|
-
const hasWindow = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
6
|
-
|
|
7
|
-
const ROUTING_TRAILING_SLASH = (typeof __ROUND_ROUTING_TRAILING_SLASH__ !== 'undefined')
|
|
8
|
-
? Boolean(__ROUND_ROUTING_TRAILING_SLASH__)
|
|
9
|
-
: true;
|
|
10
|
-
|
|
11
|
-
const currentPath = signal(hasWindow ? window.location.pathname : '/');
|
|
12
|
-
let listenerInitialized = false;
|
|
13
|
-
|
|
14
|
-
let lastPathEvaluated = null;
|
|
15
|
-
let hasMatchForPath = false;
|
|
16
|
-
|
|
17
|
-
const pathHasMatch = signal(false);
|
|
18
|
-
const pathEvalReady = signal(true);
|
|
19
|
-
|
|
20
|
-
let defaultNotFoundComponent = null;
|
|
21
|
-
let autoNotFoundMounted = false;
|
|
22
|
-
let userProvidedNotFound = false;
|
|
23
|
-
|
|
24
|
-
const RoutingContext = createContext('');
|
|
25
|
-
|
|
26
|
-
function ensureListener() {
|
|
27
|
-
if (!hasWindow || listenerInitialized) return;
|
|
28
|
-
listenerInitialized = true;
|
|
29
|
-
|
|
30
|
-
mountAutoNotFound();
|
|
31
|
-
|
|
32
|
-
window.addEventListener('popstate', () => {
|
|
33
|
-
currentPath(window.location.pathname);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getPathname() {
|
|
38
|
-
return normalizePathname(currentPath());
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function usePathname() {
|
|
42
|
-
return () => normalizePathname(currentPath());
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function getLocation() {
|
|
46
|
-
if (!hasWindow) {
|
|
47
|
-
return { pathname: normalizePathname('/'), search: '', hash: '' };
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
pathname: normalizePathname(window.location.pathname),
|
|
51
|
-
search: window.location.search ?? '',
|
|
52
|
-
hash: window.location.hash ?? ''
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function useLocation() {
|
|
57
|
-
return () => {
|
|
58
|
-
const pathname = normalizePathname(currentPath());
|
|
59
|
-
if (!hasWindow) return { pathname, search: '', hash: '' };
|
|
60
|
-
return { pathname, search: window.location.search ?? '', hash: window.location.hash ?? '' };
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function getRouteReady() {
|
|
65
|
-
const pathname = normalizePathname(currentPath());
|
|
66
|
-
return Boolean(pathEvalReady()) && lastPathEvaluated === pathname;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function useRouteReady() {
|
|
70
|
-
return () => {
|
|
71
|
-
const pathname = normalizePathname(currentPath());
|
|
72
|
-
return Boolean(pathEvalReady()) && lastPathEvaluated === pathname;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function getIsNotFound() {
|
|
77
|
-
const pathname = normalizePathname(currentPath());
|
|
78
|
-
if (pathname === '/') return false;
|
|
79
|
-
if (!(Boolean(pathEvalReady()) && lastPathEvaluated === pathname)) return false;
|
|
80
|
-
return !Boolean(pathHasMatch());
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function useIsNotFound() {
|
|
84
|
-
return () => {
|
|
85
|
-
const pathname = normalizePathname(currentPath());
|
|
86
|
-
if (pathname === '/') return false;
|
|
87
|
-
if (!(Boolean(pathEvalReady()) && lastPathEvaluated === pathname)) return false;
|
|
88
|
-
return !Boolean(pathHasMatch());
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function mountAutoNotFound() {
|
|
93
|
-
if (!hasWindow || autoNotFoundMounted) return;
|
|
94
|
-
autoNotFoundMounted = true;
|
|
95
|
-
|
|
96
|
-
const host = document.getElementById('app') ?? document.body;
|
|
97
|
-
const root = document.createElement('div');
|
|
98
|
-
root.setAttribute('data-round-auto-notfound', '1');
|
|
99
|
-
host.appendChild(root);
|
|
100
|
-
|
|
101
|
-
const view = createElement('span', { style: { display: 'contents' } }, () => {
|
|
102
|
-
if (userProvidedNotFound) return null;
|
|
103
|
-
|
|
104
|
-
const pathname = normalizePathname(currentPath());
|
|
105
|
-
const ready = pathEvalReady();
|
|
106
|
-
const hasMatch = pathHasMatch();
|
|
107
|
-
|
|
108
|
-
if (!ready) return null;
|
|
109
|
-
if (lastPathEvaluated !== pathname) return null;
|
|
110
|
-
if (hasMatch) return null;
|
|
111
|
-
|
|
112
|
-
// Skip absolute 404 overlay for the root path if no match found,
|
|
113
|
-
// allowing the base app to render its non-routed content.
|
|
114
|
-
if (pathname === '/') return null;
|
|
115
|
-
|
|
116
|
-
const Comp = defaultNotFoundComponent;
|
|
117
|
-
if (typeof Comp === 'function') {
|
|
118
|
-
return createElement(Comp, { pathname });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return createElement('div', { style: { padding: '16px' } },
|
|
122
|
-
createElement('h1', null, '404'),
|
|
123
|
-
createElement('p', null, 'Page not found: ', pathname)
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
root.appendChild(view);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Navigate to a different path programmatically.
|
|
132
|
-
* @param {string} to The destination URL or path.
|
|
133
|
-
* @param {object} [options] Navigation options (e.g., { replace: true }).
|
|
134
|
-
*/
|
|
135
|
-
export function navigate(to, options = {}) {
|
|
136
|
-
if (!hasWindow) return;
|
|
137
|
-
ensureListener();
|
|
138
|
-
|
|
139
|
-
const normalizedTo = normalizeTo(to);
|
|
140
|
-
const replace = Boolean(options.replace);
|
|
141
|
-
if (replace) window.history.replaceState({}, '', normalizedTo);
|
|
142
|
-
else window.history.pushState({}, '', normalizedTo);
|
|
143
|
-
|
|
144
|
-
currentPath(window.location.pathname);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function applyHead({ title, meta, links, icon, favicon }) {
|
|
148
|
-
if (!hasWindow) return;
|
|
149
|
-
|
|
150
|
-
if (typeof title === 'string') {
|
|
151
|
-
document.title = title;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
document.querySelectorAll('[data-round-head="1"]').forEach((n) => n.remove());
|
|
155
|
-
|
|
156
|
-
const iconHref = icon ?? favicon;
|
|
157
|
-
if (typeof iconHref === 'string' && iconHref.length) {
|
|
158
|
-
const el = document.createElement('link');
|
|
159
|
-
el.setAttribute('data-round-head', '1');
|
|
160
|
-
el.setAttribute('rel', 'icon');
|
|
161
|
-
el.setAttribute('href', iconHref);
|
|
162
|
-
document.head.appendChild(el);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (Array.isArray(links)) {
|
|
166
|
-
links.forEach((l) => {
|
|
167
|
-
if (!l || typeof l !== 'object') return;
|
|
168
|
-
const el = document.createElement('link');
|
|
169
|
-
el.setAttribute('data-round-head', '1');
|
|
170
|
-
Object.entries(l).forEach(([k, v]) => {
|
|
171
|
-
if (v === null || v === undefined) return;
|
|
172
|
-
el.setAttribute(k, String(v));
|
|
173
|
-
});
|
|
174
|
-
document.head.appendChild(el);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (Array.isArray(meta)) {
|
|
179
|
-
meta.forEach((entry) => {
|
|
180
|
-
if (!entry) return;
|
|
181
|
-
const el = document.createElement('meta');
|
|
182
|
-
el.setAttribute('data-round-head', '1');
|
|
183
|
-
|
|
184
|
-
if (Array.isArray(entry) && entry.length >= 2) {
|
|
185
|
-
const [name, content] = entry;
|
|
186
|
-
if (typeof name === 'string') el.setAttribute('name', name);
|
|
187
|
-
el.setAttribute('content', String(content ?? ''));
|
|
188
|
-
} else if (typeof entry === 'object') {
|
|
189
|
-
Object.entries(entry).forEach(([k, v]) => {
|
|
190
|
-
if (v === null || v === undefined) return;
|
|
191
|
-
el.setAttribute(k, String(v));
|
|
192
|
-
});
|
|
193
|
-
} else {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
document.head.appendChild(el);
|
|
198
|
-
});
|
|
199
|
-
} else if (meta && typeof meta === 'object') {
|
|
200
|
-
Object.entries(meta).forEach(([name, content]) => {
|
|
201
|
-
if (typeof name !== 'string') return;
|
|
202
|
-
const el = document.createElement('meta');
|
|
203
|
-
el.setAttribute('data-round-head', '1');
|
|
204
|
-
el.setAttribute('name', name);
|
|
205
|
-
el.setAttribute('content', String(content ?? ''));
|
|
206
|
-
document.head.appendChild(el);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function startHead(_head) {
|
|
212
|
-
return _head;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function splitUrl(url) {
|
|
216
|
-
const str = String(url ?? '');
|
|
217
|
-
const hashIdx = str.indexOf('#');
|
|
218
|
-
const queryIdx = str.indexOf('?');
|
|
219
|
-
const cutIdx = (hashIdx === -1)
|
|
220
|
-
? queryIdx
|
|
221
|
-
: (queryIdx === -1 ? hashIdx : Math.min(hashIdx, queryIdx));
|
|
222
|
-
|
|
223
|
-
if (cutIdx === -1) return { path: str, suffix: '' };
|
|
224
|
-
return { path: str.slice(0, cutIdx), suffix: str.slice(cutIdx) };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function normalizePathname(p) {
|
|
228
|
-
let pathname = String(p ?? '/');
|
|
229
|
-
if (!pathname.startsWith('/')) pathname = '/' + pathname;
|
|
230
|
-
if (pathname.length > 1) {
|
|
231
|
-
if (ROUTING_TRAILING_SLASH) {
|
|
232
|
-
if (!pathname.endsWith('/')) pathname += '/';
|
|
233
|
-
} else {
|
|
234
|
-
if (pathname.endsWith('/')) pathname = pathname.slice(0, -1);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return pathname;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function normalizeTo(to) {
|
|
241
|
-
const { path, suffix } = splitUrl(to);
|
|
242
|
-
if (!path.startsWith('/')) return String(to ?? '');
|
|
243
|
-
return normalizePathname(path) + suffix;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function matchRoute(route, pathname, exact = true) {
|
|
247
|
-
const r = normalizePathname(route);
|
|
248
|
-
const p = normalizePathname(pathname);
|
|
249
|
-
if (exact) return r === p;
|
|
250
|
-
// Prefix match: either exactly the same, or p starts with r plus a slash
|
|
251
|
-
return p === r || p.startsWith(r.endsWith('/') ? r : r + '/');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function beginPathEvaluation(pathname) {
|
|
255
|
-
if (pathname !== lastPathEvaluated) {
|
|
256
|
-
lastPathEvaluated = pathname;
|
|
257
|
-
hasMatchForPath = false;
|
|
258
|
-
pathHasMatch(false);
|
|
259
|
-
|
|
260
|
-
pathEvalReady(false);
|
|
261
|
-
setTimeout(() => {
|
|
262
|
-
if (lastPathEvaluated !== pathname) return;
|
|
263
|
-
pathEvalReady(true);
|
|
264
|
-
}, 0);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function setNotFound(Component) {
|
|
269
|
-
defaultNotFoundComponent = Component;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Define a route that renders its children when the path matches.
|
|
274
|
-
* @param {object} props Route properties.
|
|
275
|
-
* @param {string} [props.route='/'] The path to match.
|
|
276
|
-
* @param {boolean} [props.exact] Whether to use exact matching.
|
|
277
|
-
* @param {string} [props.title] Page title to set when active.
|
|
278
|
-
* @param {string} [props.description] Meta description to set when active.
|
|
279
|
-
* @param {any} [props.children] Content to render.
|
|
280
|
-
*/
|
|
281
|
-
export function Route(props = {}) {
|
|
282
|
-
ensureListener();
|
|
283
|
-
|
|
284
|
-
return createElement('span', { style: { display: 'contents' } }, () => {
|
|
285
|
-
const parentPath = readContext(RoutingContext) || '';
|
|
286
|
-
const pathname = normalizePathname(currentPath());
|
|
287
|
-
beginPathEvaluation(pathname);
|
|
288
|
-
|
|
289
|
-
const routeProp = props.route ?? '/';
|
|
290
|
-
if (typeof routeProp === 'string' && !routeProp.startsWith('/')) {
|
|
291
|
-
throw new Error(`Invalid route: "${routeProp}". All routes must start with a forward slash "/". (Nested under: "${parentPath || 'root'}")`);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
let fullRoute = '';
|
|
295
|
-
if (parentPath && parentPath !== '/') {
|
|
296
|
-
const cleanParent = parentPath.endsWith('/') ? parentPath.slice(0, -1) : parentPath;
|
|
297
|
-
const cleanChild = routeProp.startsWith('/') ? routeProp : '/' + routeProp;
|
|
298
|
-
|
|
299
|
-
if (cleanChild.startsWith(cleanParent + '/') || cleanChild === cleanParent) {
|
|
300
|
-
fullRoute = normalizePathname(cleanChild);
|
|
301
|
-
} else {
|
|
302
|
-
fullRoute = normalizePathname(cleanParent + cleanChild);
|
|
303
|
-
}
|
|
304
|
-
} else {
|
|
305
|
-
fullRoute = normalizePathname(routeProp);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const isRoot = fullRoute === '/';
|
|
309
|
-
const exact = props.exact !== undefined ? Boolean(props.exact) : isRoot;
|
|
310
|
-
|
|
311
|
-
// For nested routing, we match as a prefix so parents stay rendered while children are active
|
|
312
|
-
if (!matchRoute(fullRoute, pathname, exact)) return null;
|
|
313
|
-
|
|
314
|
-
// If it's an exact match of the FULL segments, mark as matched for 404 purposes
|
|
315
|
-
if (matchRoute(fullRoute, pathname, true)) {
|
|
316
|
-
hasMatchForPath = true;
|
|
317
|
-
pathHasMatch(true);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const mergedHead = (props.head && typeof props.head === 'object') ? props.head : {};
|
|
321
|
-
const meta = props.description
|
|
322
|
-
? ([{ name: 'description', content: String(props.description) }].concat(mergedHead.meta ?? props.meta ?? []))
|
|
323
|
-
: (mergedHead.meta ?? props.meta);
|
|
324
|
-
const links = mergedHead.links ?? props.links;
|
|
325
|
-
const title = mergedHead.title ?? props.title;
|
|
326
|
-
const icon = mergedHead.icon ?? props.icon;
|
|
327
|
-
const favicon = mergedHead.favicon ?? props.favicon;
|
|
328
|
-
|
|
329
|
-
applyHead({ title, meta, links, icon, favicon });
|
|
330
|
-
|
|
331
|
-
// Provide the current full path to nested routes
|
|
332
|
-
return createElement(RoutingContext.Provider, { value: fullRoute }, props.children);
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* An alias for Route, typically used for top-level pages.
|
|
338
|
-
* @param {object} props Page properties (same as Route).
|
|
339
|
-
*/
|
|
340
|
-
export function Page(props = {}) {
|
|
341
|
-
ensureListener();
|
|
342
|
-
|
|
343
|
-
return createElement('span', { style: { display: 'contents' } }, () => {
|
|
344
|
-
const parentPath = readContext(RoutingContext) || '';
|
|
345
|
-
const pathname = normalizePathname(currentPath());
|
|
346
|
-
beginPathEvaluation(pathname);
|
|
347
|
-
|
|
348
|
-
const routeProp = props.route ?? '/';
|
|
349
|
-
if (typeof routeProp === 'string' && !routeProp.startsWith('/')) {
|
|
350
|
-
throw new Error(`Invalid route: "${routeProp}". All routes must start with a forward slash "/". (Nested under: "${parentPath || 'root'}")`);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
let fullRoute = '';
|
|
354
|
-
if (parentPath && parentPath !== '/') {
|
|
355
|
-
const cleanParent = parentPath.endsWith('/') ? parentPath.slice(0, -1) : parentPath;
|
|
356
|
-
const cleanChild = routeProp.startsWith('/') ? routeProp : '/' + routeProp;
|
|
357
|
-
|
|
358
|
-
if (cleanChild.startsWith(cleanParent + '/') || cleanChild === cleanParent) {
|
|
359
|
-
fullRoute = normalizePathname(cleanChild);
|
|
360
|
-
} else {
|
|
361
|
-
fullRoute = normalizePathname(cleanParent + cleanChild);
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
fullRoute = normalizePathname(routeProp);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const isRoot = fullRoute === '/';
|
|
368
|
-
const exact = props.exact !== undefined ? Boolean(props.exact) : isRoot;
|
|
369
|
-
|
|
370
|
-
if (!matchRoute(fullRoute, pathname, exact)) return null;
|
|
371
|
-
|
|
372
|
-
if (matchRoute(fullRoute, pathname, true)) {
|
|
373
|
-
hasMatchForPath = true;
|
|
374
|
-
pathHasMatch(true);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const mergedHead = (props.head && typeof props.head === 'object') ? props.head : {};
|
|
378
|
-
const meta = props.description
|
|
379
|
-
? ([{ name: 'description', content: String(props.description) }].concat(mergedHead.meta ?? props.meta ?? []))
|
|
380
|
-
: (mergedHead.meta ?? props.meta);
|
|
381
|
-
const links = mergedHead.links ?? props.links;
|
|
382
|
-
const title = mergedHead.title ?? props.title;
|
|
383
|
-
const icon = mergedHead.icon ?? props.icon;
|
|
384
|
-
const favicon = mergedHead.favicon ?? props.favicon;
|
|
385
|
-
|
|
386
|
-
applyHead({ title, meta, links, icon, favicon });
|
|
387
|
-
|
|
388
|
-
return createElement(RoutingContext.Provider, { value: fullRoute }, props.children);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Define a fallback component or content for when no routes match.
|
|
394
|
-
*/
|
|
395
|
-
export function NotFound(props = {}) {
|
|
396
|
-
ensureListener();
|
|
397
|
-
|
|
398
|
-
userProvidedNotFound = true;
|
|
399
|
-
|
|
400
|
-
return createElement('span', { style: { display: 'contents' } }, () => {
|
|
401
|
-
const pathname = normalizePathname(currentPath());
|
|
402
|
-
beginPathEvaluation(pathname);
|
|
403
|
-
|
|
404
|
-
const ready = pathEvalReady();
|
|
405
|
-
const hasMatch = pathHasMatch();
|
|
406
|
-
if (!ready) return null;
|
|
407
|
-
if (lastPathEvaluated !== pathname) return null;
|
|
408
|
-
|
|
409
|
-
if (hasMatch) return null;
|
|
410
|
-
if (pathname === '/') return null;
|
|
411
|
-
|
|
412
|
-
const Comp = props.component ?? defaultNotFoundComponent;
|
|
413
|
-
if (typeof Comp === 'function') {
|
|
414
|
-
return createElement(Comp, { pathname });
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (props.children !== undefined) return props.children;
|
|
418
|
-
|
|
419
|
-
return createElement('div', { style: { padding: '16px' } },
|
|
420
|
-
createElement('h1', null, '404'),
|
|
421
|
-
createElement('p', null, 'Page not found: ', pathname)
|
|
422
|
-
);
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* A standard link component that performs SPA navigation.
|
|
428
|
-
* @param {object} props Link properties.
|
|
429
|
-
* @param {string} [props.href] The destination path.
|
|
430
|
-
* @param {boolean} [props.spa=true] Use SPA navigation (prevents reload).
|
|
431
|
-
* @param {any} [props.children] Link content.
|
|
432
|
-
*/
|
|
433
|
-
export function Link(props = {}) {
|
|
434
|
-
ensureListener();
|
|
435
|
-
|
|
436
|
-
const rawHref = props.href ?? props.to ?? '#';
|
|
437
|
-
const href = spaNormalizeHref(rawHref);
|
|
438
|
-
|
|
439
|
-
const spa = props.spa !== undefined ? Boolean(props.spa) : true;
|
|
440
|
-
const reload = Boolean(props.reload);
|
|
441
|
-
|
|
442
|
-
const onClick = (e) => {
|
|
443
|
-
if (typeof props.onClick === 'function') props.onClick(e);
|
|
444
|
-
if (e.defaultPrevented) return;
|
|
445
|
-
|
|
446
|
-
// Classic navigation: allow the browser to reload.
|
|
447
|
-
if (!spa || reload) return;
|
|
448
|
-
|
|
449
|
-
if (e.button !== 0) return;
|
|
450
|
-
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
451
|
-
|
|
452
|
-
e.preventDefault();
|
|
453
|
-
navigate(href);
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const { children, to, ...rest } = props;
|
|
457
|
-
const normalizedChildren = Array.isArray(children)
|
|
458
|
-
? children
|
|
459
|
-
: (children === undefined || children === null ? [] : [children]);
|
|
460
|
-
|
|
461
|
-
return createElement('a', { ...rest, href, onClick }, ...normalizedChildren);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function spaNormalizeHref(href) {
|
|
465
|
-
const str = String(href ?? '#');
|
|
466
|
-
if (!str.startsWith('/')) return str;
|
|
467
|
-
return normalizeTo(str);
|
|
468
|
-
}
|