sinwan-router 0.1.1 → 1.0.0

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/dist/index.js ADDED
@@ -0,0 +1,899 @@
1
+ // src/router.ts
2
+ import { signal, computed } from "sinwan/reactivity";
3
+
4
+ // src/matcher.ts
5
+ function createNode(prefix) {
6
+ return {
7
+ prefix,
8
+ children: [],
9
+ isParam: prefix.startsWith(":"),
10
+ paramName: prefix.startsWith(":") ? prefix.slice(1) : "",
11
+ isWildcard: prefix === "*" || prefix.startsWith("*"),
12
+ route: null
13
+ };
14
+ }
15
+ function normalizePath(path) {
16
+ const qIdx = path.indexOf("?");
17
+ if (qIdx !== -1)
18
+ path = path.slice(0, qIdx);
19
+ const hIdx = path.indexOf("#");
20
+ if (hIdx !== -1)
21
+ path = path.slice(0, hIdx);
22
+ if (!path.startsWith("/"))
23
+ path = "/" + path;
24
+ if (path.length > 1 && path.endsWith("/")) {
25
+ path = path.slice(0, -1);
26
+ }
27
+ return path;
28
+ }
29
+ function splitSegments(path) {
30
+ return path.split("/").filter(Boolean);
31
+ }
32
+ function extractSearchParams(path) {
33
+ const qIdx = path.indexOf("?");
34
+ if (qIdx === -1)
35
+ return new URLSearchParams;
36
+ return new URLSearchParams(path.slice(qIdx + 1));
37
+ }
38
+ function radixInsert(root, pattern, data) {
39
+ const segments = splitSegments(normalizePath(pattern));
40
+ insertNode(root, segments, 0, data);
41
+ }
42
+ function insertNode(node, segments, depth, data) {
43
+ if (depth === segments.length) {
44
+ node.route = data;
45
+ return;
46
+ }
47
+ const seg = segments[depth];
48
+ if (seg === undefined)
49
+ return;
50
+ if (seg === "*" || seg.startsWith("*")) {
51
+ const existing = node.children.find((c) => c.isWildcard);
52
+ if (existing) {
53
+ insertNode(existing, segments, depth + 1, data);
54
+ return;
55
+ }
56
+ const wc = createNode(seg);
57
+ node.children.push(wc);
58
+ insertNode(wc, segments, depth + 1, data);
59
+ return;
60
+ }
61
+ if (seg.startsWith(":")) {
62
+ const paramName = seg.slice(1);
63
+ for (const child of node.children) {
64
+ if (child.isParam && child.paramName === paramName) {
65
+ insertNode(child, segments, depth + 1, data);
66
+ return;
67
+ }
68
+ }
69
+ const pc = createNode(seg);
70
+ node.children.push(pc);
71
+ insertNode(pc, segments, depth + 1, data);
72
+ return;
73
+ }
74
+ for (const child of node.children) {
75
+ if (child.isParam || child.isWildcard)
76
+ continue;
77
+ if (child.prefix === seg) {
78
+ insertNode(child, segments, depth + 1, data);
79
+ return;
80
+ }
81
+ }
82
+ const sc = createNode(seg);
83
+ node.children.push(sc);
84
+ insertNode(sc, segments, depth + 1, data);
85
+ }
86
+ function radixSearch(root, path) {
87
+ const segments = splitSegments(normalizePath(path));
88
+ const params = Object.create(null);
89
+ const result = searchNode(root, segments, 0, params, "");
90
+ if (!result)
91
+ return null;
92
+ return result;
93
+ }
94
+ function searchNode(node, segments, depth, params, pathSoFar) {
95
+ if (depth === segments.length) {
96
+ if (node.route) {
97
+ return {
98
+ data: node.route,
99
+ params: copyParams(params),
100
+ matchedPath: pathSoFar
101
+ };
102
+ }
103
+ for (const child of node.children) {
104
+ if (child.isWildcard && child.route) {
105
+ const paramName = child.prefix === "*" ? "" : child.prefix.slice(1);
106
+ if (paramName) {
107
+ params[paramName] = "";
108
+ } else {
109
+ params["_wildcard"] = "";
110
+ }
111
+ return {
112
+ data: child.route,
113
+ params: copyParams(params),
114
+ matchedPath: pathSoFar + "/*"
115
+ };
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ const seg = segments[depth];
121
+ if (seg === undefined)
122
+ return null;
123
+ for (const child of node.children) {
124
+ if (child.isParam || child.isWildcard)
125
+ continue;
126
+ if (child.prefix !== seg)
127
+ continue;
128
+ const found = searchNode(child, segments, depth + 1, params, pathSoFar + "/" + child.prefix);
129
+ if (found)
130
+ return found;
131
+ }
132
+ for (const child of node.children) {
133
+ if (!child.isParam)
134
+ continue;
135
+ params[child.paramName] = decodeURIComponent(seg);
136
+ const found = searchNode(child, segments, depth + 1, params, pathSoFar + "/:" + child.paramName);
137
+ if (found)
138
+ return found;
139
+ delete params[child.paramName];
140
+ }
141
+ for (const child of node.children) {
142
+ if (!child.isWildcard)
143
+ continue;
144
+ if (!child.route)
145
+ continue;
146
+ const rest = wildcardValue(segments, depth);
147
+ const paramName = child.prefix === "*" ? "" : child.prefix.slice(1);
148
+ if (paramName) {
149
+ params[paramName] = rest;
150
+ } else {
151
+ params["_wildcard"] = rest;
152
+ }
153
+ return {
154
+ data: child.route,
155
+ params: copyParams(params),
156
+ matchedPath: pathSoFar + "/*"
157
+ };
158
+ }
159
+ return null;
160
+ }
161
+ function wildcardValue(segments, depth) {
162
+ let rest = "";
163
+ for (let i = depth;i < segments.length; i += 1) {
164
+ const seg = segments[i];
165
+ if (seg === undefined || seg === "")
166
+ continue;
167
+ if (rest === "")
168
+ rest = seg;
169
+ else
170
+ rest += "/" + seg;
171
+ }
172
+ return rest;
173
+ }
174
+ function copyParams(src) {
175
+ const dst = Object.create(null);
176
+ for (const key in src)
177
+ dst[key] = src[key];
178
+ return dst;
179
+ }
180
+ function createRadixTree() {
181
+ return createNode("");
182
+ }
183
+ function buildPath(pattern, params = {}) {
184
+ return pattern.replace(/:(\w+)/g, (_match, name) => {
185
+ const value = params[name];
186
+ if (value === undefined) {
187
+ throw new Error(`Missing param "${name}" for pattern "${pattern}"`);
188
+ }
189
+ return encodeURIComponent(value);
190
+ });
191
+ }
192
+
193
+ // src/router.ts
194
+ function hasTrueFlag(value, key) {
195
+ return key in value && value[key] === true;
196
+ }
197
+ function isLazyComponent(comp) {
198
+ if (typeof comp !== "function")
199
+ return false;
200
+ if (hasTrueFlag(comp, "_SinwanComponent"))
201
+ return false;
202
+ if (hasTrueFlag(comp, "_SinwanLazy"))
203
+ return true;
204
+ return comp.length === 0;
205
+ }
206
+ function lazy(loader) {
207
+ const factory = () => loader();
208
+ return Object.assign(factory, { _SinwanLazy: true });
209
+ }
210
+
211
+ class Router {
212
+ tree;
213
+ routeList;
214
+ _path;
215
+ _searchParams;
216
+ _matched;
217
+ _params;
218
+ lazyCache = new Map;
219
+ lazyErrors = new Map;
220
+ lazyInflight = new Map;
221
+ _lazyTick;
222
+ popstateHandler = null;
223
+ constructor(routes, initialPath) {
224
+ this.tree = createRadixTree();
225
+ this.routeList = [];
226
+ this._lazyTick = signal(0);
227
+ for (const route of routes) {
228
+ this.insertRoute(route, "");
229
+ }
230
+ const startPath = initialPath ?? (typeof window !== "undefined" ? window.location.pathname + window.location.search : "/");
231
+ this._path = signal(normalizePath(startPath));
232
+ this._searchParams = signal(extractSearchParams(startPath));
233
+ this._matched = computed(() => {
234
+ const path = normalizePath(this._path.value);
235
+ const result = radixSearch(this.tree, path);
236
+ if (!result)
237
+ return null;
238
+ const leaf = result.data;
239
+ const params = result.params;
240
+ let parent;
241
+ let matched;
242
+ for (let i = 0;i < leaf.chain.length; i++) {
243
+ const entry = leaf.chain[i];
244
+ const m = {
245
+ route: entry.route,
246
+ params,
247
+ path,
248
+ matchedPath: entry.fullPattern,
249
+ parent: i > 0 ? parent : undefined
250
+ };
251
+ parent = m;
252
+ matched = m;
253
+ }
254
+ return matched ?? null;
255
+ });
256
+ this._params = computed(() => {
257
+ const matched = this._matched.value;
258
+ return matched ? matched.params : {};
259
+ });
260
+ if (typeof window !== "undefined") {
261
+ this.popstateHandler = () => {
262
+ const url = window.location.pathname + window.location.search;
263
+ this._path.value = normalizePath(url);
264
+ this._searchParams.value = extractSearchParams(url);
265
+ };
266
+ window.addEventListener("popstate", this.popstateHandler);
267
+ }
268
+ }
269
+ insertRoute(route, parentPath, parentChain = []) {
270
+ const fullPattern = joinPaths(parentPath, route.path);
271
+ const chain = [...parentChain, { route, fullPattern }];
272
+ const leafData = { route, fullPattern, chain };
273
+ radixInsert(this.tree, fullPattern, leafData);
274
+ this.routeList.push(route);
275
+ if (route.children) {
276
+ for (const child of route.children) {
277
+ this.insertRoute(child, fullPattern, chain);
278
+ }
279
+ }
280
+ }
281
+ bumpLazyTick() {
282
+ this._lazyTick.value = this._lazyTick.value + 1;
283
+ }
284
+ get lazyTick() {
285
+ return this._lazyTick.value;
286
+ }
287
+ peekLazy(cacheKey) {
288
+ const cached = this.lazyCache.get(cacheKey);
289
+ if (cached)
290
+ return { status: "ready", component: cached };
291
+ if (this.lazyInflight.has(cacheKey))
292
+ return { status: "loading" };
293
+ if (this.lazyErrors.has(cacheKey)) {
294
+ return { status: "error", error: this.lazyErrors.get(cacheKey) };
295
+ }
296
+ return { status: "idle" };
297
+ }
298
+ ensureLazy(cacheKey, loader) {
299
+ const state = this.peekLazy(cacheKey);
300
+ if (state.status !== "idle")
301
+ return;
302
+ this.startLazyLoad(cacheKey, loader);
303
+ }
304
+ startLazyLoad(cacheKey, loader) {
305
+ this.lazyErrors.delete(cacheKey);
306
+ const pending = loader().then((mod) => {
307
+ this.lazyCache.set(cacheKey, mod.default);
308
+ this.lazyErrors.delete(cacheKey);
309
+ this.bumpLazyTick();
310
+ return mod.default;
311
+ }).catch((err) => {
312
+ this.lazyErrors.set(cacheKey, err);
313
+ this.bumpLazyTick();
314
+ throw err;
315
+ }).finally(() => {
316
+ this.lazyInflight.delete(cacheKey);
317
+ });
318
+ this.lazyInflight.set(cacheKey, pending);
319
+ pending.catch(() => {});
320
+ return pending;
321
+ }
322
+ get path() {
323
+ return this._path.value;
324
+ }
325
+ get pathSignal() {
326
+ return this._path;
327
+ }
328
+ get matched() {
329
+ return this._matched.value;
330
+ }
331
+ get matchedComputed() {
332
+ return this._matched;
333
+ }
334
+ get params() {
335
+ return this._params.value;
336
+ }
337
+ get searchParams() {
338
+ return this._searchParams.value;
339
+ }
340
+ get routes() {
341
+ return this.routeList;
342
+ }
343
+ navigate(to, options = {}) {
344
+ const normalized = normalizePath(to);
345
+ if (typeof window !== "undefined") {
346
+ if (options.replace) {
347
+ window.history.replaceState(options.state ?? {}, "", to);
348
+ } else {
349
+ window.history.pushState(options.state ?? {}, "", to);
350
+ }
351
+ }
352
+ this._path.value = normalized;
353
+ this._searchParams.value = extractSearchParams(to);
354
+ }
355
+ prefetch(path) {
356
+ if (typeof window === "undefined")
357
+ return;
358
+ const normalized = normalizePath(path);
359
+ const result = radixSearch(this.tree, normalized);
360
+ if (!result)
361
+ return;
362
+ const leaf = result.data;
363
+ const route = leaf.route;
364
+ if (!isLazyComponent(route.component))
365
+ return;
366
+ const cacheKey = leaf.fullPattern;
367
+ const state = this.peekLazy(cacheKey);
368
+ if (state.status === "ready" || state.status === "loading")
369
+ return;
370
+ this.startLazyLoad(cacheKey, route.component);
371
+ }
372
+ async resolveComponent(route) {
373
+ if (!isLazyComponent(route.component)) {
374
+ return route.component;
375
+ }
376
+ const cacheKey = this.findFullPattern(route) ?? route.path;
377
+ const cached = this.lazyCache.get(cacheKey);
378
+ if (cached)
379
+ return cached;
380
+ const inflight = this.lazyInflight.get(cacheKey);
381
+ if (inflight)
382
+ return inflight;
383
+ return this.startLazyLoad(cacheKey, route.component);
384
+ }
385
+ findFullPattern(route) {
386
+ return this.searchForRoute(this.tree, route, "");
387
+ }
388
+ searchForRoute(node, target, pathSoFar) {
389
+ if (node.route) {
390
+ const leaf = node.route;
391
+ if (leaf.route === target)
392
+ return leaf.fullPattern;
393
+ }
394
+ for (const child of node.children) {
395
+ const seg = child.isParam ? "/:" + child.paramName : child.isWildcard ? "/*" : "/" + child.prefix;
396
+ const found = this.searchForRoute(child, target, pathSoFar + seg);
397
+ if (found)
398
+ return found;
399
+ }
400
+ return;
401
+ }
402
+ dispose() {
403
+ if (this.popstateHandler && typeof window !== "undefined") {
404
+ window.removeEventListener("popstate", this.popstateHandler);
405
+ this.popstateHandler = null;
406
+ }
407
+ this.lazyCache.clear();
408
+ this.lazyErrors.clear();
409
+ this.lazyInflight.clear();
410
+ }
411
+ }
412
+ function joinPaths(parent, child) {
413
+ if (child === "/")
414
+ return parent || "/";
415
+ const relativeChild = child.startsWith("/") ? child.slice(1) : child;
416
+ if (parent === "/" || parent === "")
417
+ return "/" + relativeChild;
418
+ return parent + "/" + relativeChild;
419
+ }
420
+ function createRouter(routes, initialPath) {
421
+ return new Router(routes, initialPath);
422
+ }
423
+ // src/components.tsx
424
+ import { _$createTemplate } from "sinwan/renderer";
425
+ import { cc } from "sinwan/component";
426
+ import { Key } from "sinwan/component";
427
+ import { computed as computed2 } from "sinwan/reactivity";
428
+
429
+ // src/hooks.ts
430
+ import { inject } from "sinwan/component";
431
+ var RouterKey = Symbol("sinwan.router");
432
+ function useRouter() {
433
+ const router = inject(RouterKey);
434
+ if (!router) {
435
+ throw new Error("useRouter() called outside of a Router provider.");
436
+ }
437
+ return router;
438
+ }
439
+ function useParams() {
440
+ return useRouter().params;
441
+ }
442
+ function useSearchParams() {
443
+ return useRouter().searchParams;
444
+ }
445
+ function useRoute() {
446
+ return useRouter().matched;
447
+ }
448
+ function useNavigate() {
449
+ const router = useRouter();
450
+ return router.navigate.bind(router);
451
+ }
452
+ function usePath() {
453
+ return useRouter().path;
454
+ }
455
+
456
+ // src/link-nav.ts
457
+ function jsxClass(value) {
458
+ return value;
459
+ }
460
+ function isExternalHref(href) {
461
+ if (href.startsWith("//"))
462
+ return true;
463
+ const colon = href.indexOf(":");
464
+ if (colon <= 0)
465
+ return false;
466
+ const slash = href.indexOf("/");
467
+ if (slash !== -1 && slash < colon)
468
+ return false;
469
+ return true;
470
+ }
471
+ function isHashOnlyHref(href, currentPath) {
472
+ if (href.startsWith("#"))
473
+ return true;
474
+ const hashIdx = href.indexOf("#");
475
+ if (hashIdx === -1)
476
+ return false;
477
+ const beforeHash = href.slice(0, hashIdx);
478
+ if (beforeHash === "")
479
+ return true;
480
+ const qIdx = beforeHash.indexOf("?");
481
+ const pathPart = qIdx === -1 ? beforeHash : beforeHash.slice(0, qIdx);
482
+ if (pathPart === "" || pathPart === ".")
483
+ return true;
484
+ const normalized = pathPart.startsWith("/") ? pathPart : "/" + pathPart;
485
+ const stripped = normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
486
+ return stripped === currentPath;
487
+ }
488
+ function shouldInterceptLinkClick(event, href, currentPath, options = {}) {
489
+ if (event.defaultPrevented)
490
+ return false;
491
+ if (event.button !== 0)
492
+ return false;
493
+ if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
494
+ return false;
495
+ }
496
+ if (options.target != null && options.target !== "" && options.target !== "_self") {
497
+ return false;
498
+ }
499
+ if (options.download !== undefined && options.download !== false) {
500
+ return false;
501
+ }
502
+ if (isExternalHref(href))
503
+ return false;
504
+ if (isHashOnlyHref(href, currentPath))
505
+ return false;
506
+ return true;
507
+ }
508
+
509
+ // src/outlet.ts
510
+ function getMatchAtDepth(matched, depth) {
511
+ if (!matched)
512
+ return null;
513
+ let current = matched;
514
+ const chain = [];
515
+ while (current) {
516
+ chain.unshift(current);
517
+ current = current.parent;
518
+ }
519
+ return chain[depth] ?? null;
520
+ }
521
+ function routeIdentity(matched) {
522
+ const pattern = matched.matchedPath;
523
+ if (pattern.includes("*")) {
524
+ return matched.path;
525
+ }
526
+ let concrete = pattern;
527
+ for (const key of Object.keys(matched.params)) {
528
+ const value = matched.params[key];
529
+ if (value === undefined)
530
+ continue;
531
+ concrete = concrete.replace(":" + key, encodeURIComponent(value));
532
+ }
533
+ if (concrete.includes(":")) {
534
+ return matched.path;
535
+ }
536
+ return concrete;
537
+ }
538
+ function lazyStatusSuffix(router, matched) {
539
+ if (!isLazyComponent(matched.route.component))
540
+ return "sync";
541
+ const state = router.peekLazy(matched.matchedPath);
542
+ return state.status;
543
+ }
544
+ function computeOutletKey(router, depth) {
545
+ router.lazyTick;
546
+ const matched = getMatchAtDepth(router.matched, depth);
547
+ if (!matched) {
548
+ return depth > 0 ? `__empty:${depth}` : "__notfound__";
549
+ }
550
+ return `${routeIdentity(matched)}:${lazyStatusSuffix(router, matched)}`;
551
+ }
552
+
553
+ // src/components.tsx
554
+ import { jsxDEV } from "sinwan/jsx-dev-runtime";
555
+ var _$tmpl_0 = {
556
+ html: '<a href="" class="" target="" download="" rel="" onclick="" onmouseenter=""><!--s:0--></a>',
557
+ slots: [{
558
+ path: [],
559
+ type: "attr",
560
+ name: "href"
561
+ }, {
562
+ path: [],
563
+ type: "attr",
564
+ name: "class"
565
+ }, {
566
+ path: [],
567
+ type: "attr",
568
+ name: "target"
569
+ }, {
570
+ path: [],
571
+ type: "attr",
572
+ name: "download"
573
+ }, {
574
+ path: [],
575
+ type: "attr",
576
+ name: "rel"
577
+ }, {
578
+ path: [],
579
+ type: "event",
580
+ name: "onclick"
581
+ }, {
582
+ path: [],
583
+ type: "event",
584
+ name: "onmouseenter"
585
+ }, {
586
+ path: [0],
587
+ type: "child"
588
+ }]
589
+ };
590
+ var _$tmpl_1 = {
591
+ html: '<a href="" class="" target="" download="" rel="" onclick="" onmouseenter=""><!--s:0--></a>',
592
+ slots: [{
593
+ path: [],
594
+ type: "attr",
595
+ name: "href"
596
+ }, {
597
+ path: [],
598
+ type: "attr",
599
+ name: "class"
600
+ }, {
601
+ path: [],
602
+ type: "attr",
603
+ name: "target"
604
+ }, {
605
+ path: [],
606
+ type: "attr",
607
+ name: "download"
608
+ }, {
609
+ path: [],
610
+ type: "attr",
611
+ name: "rel"
612
+ }, {
613
+ path: [],
614
+ type: "event",
615
+ name: "onclick"
616
+ }, {
617
+ path: [],
618
+ type: "event",
619
+ name: "onmouseenter"
620
+ }, {
621
+ path: [0],
622
+ type: "child"
623
+ }]
624
+ };
625
+ var _$tmpl_2 = {
626
+ html: "<div>404 - Page not found</div>",
627
+ slots: []
628
+ };
629
+ var _$tmpl_3 = {
630
+ html: "<div>Loading...</div>",
631
+ slots: []
632
+ };
633
+ var _$tmpl_4 = {
634
+ html: "<div>Failed to load page</div>",
635
+ slots: []
636
+ };
637
+ function onLinkClick(event, href, router, options) {
638
+ if (!shouldInterceptLinkClick(event, href, router.path, options))
639
+ return;
640
+ event.preventDefault();
641
+ router.navigate(href);
642
+ }
643
+ function downloadAttr(download) {
644
+ if (download === undefined || download === false)
645
+ return;
646
+ if (download === true)
647
+ return "";
648
+ return download;
649
+ }
650
+ var Link = cc(({
651
+ href,
652
+ prefetch = true,
653
+ class: cls,
654
+ children,
655
+ target,
656
+ download,
657
+ rel
658
+ }) => {
659
+ const router = useRouter();
660
+ return _$createTemplate(_$tmpl_0, [href, cls, target, downloadAttr(download), rel, (e) => {
661
+ onLinkClick(e, href, router, {
662
+ target,
663
+ download
664
+ });
665
+ }, () => {
666
+ if (prefetch)
667
+ router.prefetch(href);
668
+ }, children]);
669
+ });
670
+ var NavLink = cc(({
671
+ href,
672
+ prefetch = true,
673
+ activeClass = "active",
674
+ class: cls,
675
+ children,
676
+ target,
677
+ download,
678
+ rel
679
+ }) => {
680
+ const router = useRouter();
681
+ const className = computed2(() => router.path === href || router.path.startsWith(href + "/") ? `${cls ?? ""} ${activeClass}`.trim() : cls ?? "");
682
+ return _$createTemplate(_$tmpl_1, [href, jsxClass(() => className.value), target, downloadAttr(download), rel, (e) => {
683
+ onLinkClick(e, href, router, {
684
+ target,
685
+ download
686
+ });
687
+ }, () => {
688
+ if (prefetch)
689
+ router.prefetch(href);
690
+ }, children]);
691
+ });
692
+ var DefaultNotFound = cc(() => _$createTemplate(_$tmpl_2, []));
693
+ var DefaultLoading = cc(() => _$createTemplate(_$tmpl_3, []));
694
+ var DefaultError = cc(() => _$createTemplate(_$tmpl_4, []));
695
+ function resolveOutletNode(matched, router, fallback, errorComp) {
696
+ const route = matched.route;
697
+ if (!isLazyComponent(route.component)) {
698
+ const Comp = route.component;
699
+ return /* @__PURE__ */ jsxDEV(Comp, {}, undefined, false, undefined, this);
700
+ }
701
+ const cacheKey = matched.matchedPath;
702
+ const state = router.peekLazy(cacheKey);
703
+ if (state.status === "ready") {
704
+ const Comp = state.component;
705
+ return /* @__PURE__ */ jsxDEV(Comp, {}, undefined, false, undefined, this);
706
+ }
707
+ if (state.status === "error") {
708
+ const ErrorView = errorComp;
709
+ return /* @__PURE__ */ jsxDEV(ErrorView, {
710
+ error: state.error
711
+ }, undefined, false, undefined, this);
712
+ }
713
+ router.ensureLazy(cacheKey, route.component);
714
+ const Fallback = fallback;
715
+ return /* @__PURE__ */ jsxDEV(Fallback, {}, undefined, false, undefined, this);
716
+ }
717
+ var RouterOutlet = cc(({
718
+ fallback,
719
+ notFound,
720
+ error,
721
+ depth = 0
722
+ }) => {
723
+ const router = useRouter();
724
+ const Fallback = fallback ?? DefaultLoading;
725
+ const NotFound = notFound ?? DefaultNotFound;
726
+ const ErrorView = error ?? DefaultError;
727
+ return /* @__PURE__ */ jsxDEV(Key, {
728
+ when: () => computeOutletKey(router, depth),
729
+ children: () => {
730
+ const matched = getMatchAtDepth(router.matched, depth);
731
+ if (!matched) {
732
+ if (depth > 0)
733
+ return null;
734
+ return /* @__PURE__ */ jsxDEV(NotFound, {}, undefined, false, undefined, this);
735
+ }
736
+ return resolveOutletNode(matched, router, Fallback, ErrorView);
737
+ }
738
+ }, undefined, false, undefined, this);
739
+ });
740
+ // src/jsx-routes.tsx
741
+ import { cc as cc2, provide, onDispose } from "sinwan/component";
742
+ import { jsxDEV as jsxDEV2 } from "sinwan/jsx-dev-runtime";
743
+ var ROUTE_TYPE = Symbol.for("sinwan.router.Route");
744
+ function isElementLike(value) {
745
+ return value != null && typeof value === "object" && "tag" in value;
746
+ }
747
+ function routePropsRecord(props) {
748
+ const record = {
749
+ path: props.path,
750
+ component: props.component
751
+ };
752
+ if (props.meta !== undefined)
753
+ record.meta = props.meta;
754
+ if (props.children !== undefined)
755
+ record.children = props.children;
756
+ return record;
757
+ }
758
+ function Route(props) {
759
+ return {
760
+ tag: ROUTE_TYPE,
761
+ props: routePropsRecord(props),
762
+ children: []
763
+ };
764
+ }
765
+ function isRouteElement(value) {
766
+ if (!isElementLike(value))
767
+ return false;
768
+ return value.tag === ROUTE_TYPE || value.tag === Route;
769
+ }
770
+ function isRouteDefinition(value) {
771
+ if (value == null || typeof value !== "object")
772
+ return false;
773
+ if ("tag" in value)
774
+ return false;
775
+ const rec = value;
776
+ return typeof rec.path === "string" && rec.component != null;
777
+ }
778
+ function flattenNodes(node) {
779
+ if (node == null || typeof node === "boolean")
780
+ return [];
781
+ if (Array.isArray(node)) {
782
+ const out = [];
783
+ for (const child of node) {
784
+ out.push(...flattenNodes(child));
785
+ }
786
+ return out;
787
+ }
788
+ if (isElementLike(node) && node.tag === "") {
789
+ const fromKids = flattenNodes(node.children);
790
+ if (fromKids.length > 0)
791
+ return fromKids;
792
+ return flattenNodes(node.props.children);
793
+ }
794
+ return [node];
795
+ }
796
+ function nestedDefinitions(source) {
797
+ if (source == null)
798
+ return;
799
+ if (Array.isArray(source) && source.length > 0 && isRouteDefinition(source[0])) {
800
+ const defs = [];
801
+ for (const item of source) {
802
+ if (isRouteDefinition(item))
803
+ defs.push(item);
804
+ }
805
+ return defs.length > 0 ? defs : undefined;
806
+ }
807
+ if (isRouteDefinition(source))
808
+ return [source];
809
+ const collected = collectRouteDefinitions(source);
810
+ return collected.length > 0 ? collected : undefined;
811
+ }
812
+ function routeNodeToDefinition(node) {
813
+ if (isRouteDefinition(node)) {
814
+ const def = {
815
+ path: node.path,
816
+ component: node.component
817
+ };
818
+ if (node.meta)
819
+ def.meta = node.meta;
820
+ if (node.children && node.children.length > 0)
821
+ def.children = node.children;
822
+ return def;
823
+ }
824
+ if (!isRouteElement(node))
825
+ return null;
826
+ const props = node.props;
827
+ const path = props.path;
828
+ const component = props.component;
829
+ if (typeof path !== "string" || component == null)
830
+ return null;
831
+ const def = {
832
+ path,
833
+ component
834
+ };
835
+ const meta = props.meta;
836
+ if (meta != null && typeof meta === "object") {
837
+ def.meta = meta;
838
+ }
839
+ const nested = nestedDefinitions(props.children ?? node.children);
840
+ if (nested)
841
+ def.children = nested;
842
+ return def;
843
+ }
844
+ function collectRouteDefinitions(children) {
845
+ const defs = [];
846
+ for (const node of flattenNodes(children)) {
847
+ const def = routeNodeToDefinition(node);
848
+ if (def)
849
+ defs.push(def);
850
+ }
851
+ return defs;
852
+ }
853
+ var Routes = cc2(({
854
+ children,
855
+ fallback,
856
+ notFound,
857
+ error,
858
+ initialPath
859
+ }) => {
860
+ const router = createRouter(collectRouteDefinitions(children), initialPath);
861
+ provide(RouterKey, router);
862
+ onDispose(() => {
863
+ router.dispose();
864
+ });
865
+ return /* @__PURE__ */ jsxDEV2(RouterOutlet, {
866
+ fallback,
867
+ notFound,
868
+ error
869
+ }, undefined, false, undefined, this);
870
+ });
871
+ export {
872
+ Link,
873
+ NavLink,
874
+ Route,
875
+ Router,
876
+ RouterKey,
877
+ RouterOutlet,
878
+ Routes,
879
+ buildPath,
880
+ collectRouteDefinitions,
881
+ createRadixTree,
882
+ createRouter,
883
+ extractSearchParams,
884
+ isLazyComponent,
885
+ isRouteElement,
886
+ lazy,
887
+ normalizePath,
888
+ radixInsert,
889
+ radixSearch,
890
+ useNavigate,
891
+ useParams,
892
+ usePath,
893
+ useRoute,
894
+ useRouter,
895
+ useSearchParams
896
+ };
897
+
898
+ //# debugId=B05954AB6554A08764756E2164756E21
899
+ //# sourceMappingURL=index.js.map