@ilha/router 0.11.9 → 0.11.10
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.d.ts +30 -1
- package/dist/index.js +87 -22
- package/dist/{plugin-Cn_H92L0.js → plugin-FVaWBiNQ.js} +6 -3
- package/dist/rsbuild.js +1 -1
- package/dist/vite.js +1 -1
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -90,7 +90,9 @@ export interface RenderPageOptions {
|
|
|
90
90
|
markers?: boolean;
|
|
91
91
|
}
|
|
92
92
|
export interface RouterBuilder {
|
|
93
|
-
route: (pattern: string, page: Page
|
|
93
|
+
route: (pattern: string, page: Page, opts?: {
|
|
94
|
+
layouts?: LayoutHandler[];
|
|
95
|
+
}) => RouterBuilder;
|
|
94
96
|
errorBoundary: (pattern: string, handler: ErrorHandler) => RouterBuilder;
|
|
95
97
|
routes: () => RouteRecord[];
|
|
96
98
|
prime: () => void;
|
|
@@ -134,6 +136,33 @@ export declare const prime: () => void;
|
|
|
134
136
|
export declare const beforeNavigate: (fn: BeforeNavigateHook) => (() => void);
|
|
135
137
|
export declare const afterNavigate: (fn: AfterNavigateHook) => (() => void);
|
|
136
138
|
export declare const navigate: (to: string, opts?: NavigateOptions) => void;
|
|
139
|
+
export interface SearchParamOptions<T> {
|
|
140
|
+
/** Value when the param is absent. Writing it removes the param. */
|
|
141
|
+
default: T;
|
|
142
|
+
/** Decode the raw param. Default: the raw string. */
|
|
143
|
+
parse?: (raw: string) => T;
|
|
144
|
+
/** Encode a value for the URL. Default: `String(value)`. */
|
|
145
|
+
serialize?: (value: T) => string;
|
|
146
|
+
}
|
|
147
|
+
export interface SearchParam<T> {
|
|
148
|
+
(): T;
|
|
149
|
+
set: (next: T) => void;
|
|
150
|
+
update: (f: (current: T) => T) => void;
|
|
151
|
+
}
|
|
152
|
+
export interface SearchParamFn {
|
|
153
|
+
/** String param: `parse` is optional. */
|
|
154
|
+
(name: string, opts: SearchParamOptions<string>): SearchParam<string>;
|
|
155
|
+
/** Non-string param: `parse` is required to decode the raw string. */
|
|
156
|
+
<T>(name: string, opts: SearchParamOptions<T> & {
|
|
157
|
+
parse: (raw: string) => T;
|
|
158
|
+
}): SearchParam<T>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Read and write one query param through the router. Writes `replace` the
|
|
162
|
+
* history entry without scrolling, so the mounted router re-renders the
|
|
163
|
+
* current route in place instead of remounting it.
|
|
164
|
+
*/
|
|
165
|
+
export declare const searchParam: SearchParamFn;
|
|
137
166
|
export type LinkInterceptionOptions = Record<string, never>;
|
|
138
167
|
export declare const enableLinkInterception: (root?: Element | Document, _options?: LinkInterceptionOptions) => (() => void);
|
|
139
168
|
export interface IsActiveOptions {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as REQUEST_ALS_KEY } from "./als-key-CVxbuM3z.js";
|
|
2
2
|
import { a as withHeadStore, i as serializeHead, n as head, r as openBrowserHead, t as applyHeadEntriesToDocument } from "./head-WG_AtbdX.js";
|
|
3
|
-
import { h, mount, renderToString } from "ilha";
|
|
3
|
+
import { ErrorBoundary, atom, h, mount, renderToString } from "ilha";
|
|
4
4
|
|
|
5
5
|
//#region src/hash.ts
|
|
6
6
|
const isBrowser$1 = globalThis.window !== void 0 && globalThis.document !== void 0;
|
|
@@ -448,6 +448,36 @@ const navigate = (to, opts = {}) => {
|
|
|
448
448
|
type
|
|
449
449
|
});
|
|
450
450
|
};
|
|
451
|
+
/**
|
|
452
|
+
* Read and write one query param through the router. Writes `replace` the
|
|
453
|
+
* history entry without scrolling, so the mounted router re-renders the
|
|
454
|
+
* current route in place instead of remounting it.
|
|
455
|
+
*/
|
|
456
|
+
const searchParam = (name, opts) => {
|
|
457
|
+
const serialize = opts.serialize ?? String;
|
|
458
|
+
const read = () => {
|
|
459
|
+
const raw = new URLSearchParams(_search).get(name);
|
|
460
|
+
if (raw === null) return opts.default;
|
|
461
|
+
return opts.parse ? opts.parse(raw) : raw;
|
|
462
|
+
};
|
|
463
|
+
const set = (next) => {
|
|
464
|
+
const params = new URLSearchParams(_search);
|
|
465
|
+
const encoded = serialize(next);
|
|
466
|
+
if (encoded === serialize(opts.default)) params.delete(name);
|
|
467
|
+
else params.set(name, encoded);
|
|
468
|
+
const qs = params.toString();
|
|
469
|
+
navigate(`${_path}${qs ? `?${qs}` : ""}${_hash}`, {
|
|
470
|
+
replace: true,
|
|
471
|
+
scroll: false
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
return Object.assign(read, {
|
|
475
|
+
set,
|
|
476
|
+
update: (f) => {
|
|
477
|
+
set(f(read()));
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
};
|
|
451
481
|
const NON_NAV_SCHEME = /^(?:mailto|tel|javascript):/iu;
|
|
452
482
|
const resolveClickTarget = (_href, url) => {
|
|
453
483
|
if (getHistoryMode() === "hash") {
|
|
@@ -516,10 +546,16 @@ const resolveRequestUrl = (urlOrRequest) => {
|
|
|
516
546
|
return new URL("http://localhost/");
|
|
517
547
|
}
|
|
518
548
|
};
|
|
519
|
-
const
|
|
549
|
+
const composedPageForPath = (pathname, routes, notFound) => {
|
|
520
550
|
const match = matchRoute(routes, pathname);
|
|
521
|
-
if (match) return
|
|
522
|
-
|
|
551
|
+
if (!match) return notFound;
|
|
552
|
+
const { layouts } = match.data;
|
|
553
|
+
let { page } = match.data;
|
|
554
|
+
for (let j = layouts.length - 1; j >= 0; j -= 1) {
|
|
555
|
+
const layout = layouts[j];
|
|
556
|
+
if (layout) page = wrapLayout(layout, page);
|
|
557
|
+
}
|
|
558
|
+
return page;
|
|
523
559
|
};
|
|
524
560
|
const renderPage = (page, opts) => renderToString(page, opts);
|
|
525
561
|
const buildRespondBody = (result, respondOpts, head) => {
|
|
@@ -577,25 +613,51 @@ const router = (options = {}) => {
|
|
|
577
613
|
let mounted = true;
|
|
578
614
|
const prevScroll = "scrollRestoration" in history ? history.scrollRestoration : null;
|
|
579
615
|
if (prevScroll !== null) history.scrollRestoration = "manual";
|
|
580
|
-
let
|
|
616
|
+
let unmountShell = null;
|
|
581
617
|
let headSession = null;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
618
|
+
let bumpShell = null;
|
|
619
|
+
const Shell = () => {
|
|
620
|
+
const tick = atom(0);
|
|
621
|
+
bumpShell = () => {
|
|
622
|
+
tick.update((t) => t + 1);
|
|
623
|
+
};
|
|
624
|
+
tick();
|
|
625
|
+
const match = matchRoute(routes, _path);
|
|
626
|
+
if (!match) return notFound ? h(notFound, null) : "";
|
|
627
|
+
const { errorHandler, layouts, page } = match.data;
|
|
628
|
+
let view = h(page, null);
|
|
629
|
+
for (let j = layouts.length - 1; j >= 0; j -= 1) {
|
|
630
|
+
const layout = layouts[j];
|
|
631
|
+
if (layout) view = h(layout, null, view);
|
|
591
632
|
}
|
|
633
|
+
if (errorHandler) {
|
|
634
|
+
const onBoundary = errorHandler;
|
|
635
|
+
view = h(ErrorBoundary, { fallback: ({ error }) => onBoundary({ message: error.message }, snapshot()) }, view);
|
|
636
|
+
}
|
|
637
|
+
return view;
|
|
638
|
+
};
|
|
639
|
+
const mountShell = () => {
|
|
640
|
+
headSession?.close();
|
|
592
641
|
const session = openBrowserHead();
|
|
593
642
|
headSession = session;
|
|
594
|
-
|
|
643
|
+
unmountShell = mount(host, Shell, { hydrate });
|
|
595
644
|
queueMicrotask(() => {
|
|
596
645
|
session.flush();
|
|
597
646
|
});
|
|
598
647
|
};
|
|
648
|
+
const refresh = () => {
|
|
649
|
+
if (bumpShell) {
|
|
650
|
+
headSession?.close();
|
|
651
|
+
const session = openBrowserHead();
|
|
652
|
+
headSession = session;
|
|
653
|
+
bumpShell();
|
|
654
|
+
queueMicrotask(() => {
|
|
655
|
+
session.flush();
|
|
656
|
+
});
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
mountShell();
|
|
660
|
+
};
|
|
599
661
|
const popHandler = () => {
|
|
600
662
|
if (!mounted) return;
|
|
601
663
|
const prevPath = _path + _search + _hash;
|
|
@@ -611,18 +673,20 @@ const router = (options = {}) => {
|
|
|
611
673
|
to: _path + _search + _hash,
|
|
612
674
|
type: "pop"
|
|
613
675
|
});
|
|
614
|
-
|
|
676
|
+
refresh();
|
|
615
677
|
};
|
|
616
678
|
navChangeCleanup = getAdapter().onChange(popHandler);
|
|
617
679
|
linkCleanup = mountInterceptLinks ?? defaultInterceptLinks ? enableLinkInterception(document) : null;
|
|
618
|
-
|
|
680
|
+
mountShell();
|
|
619
681
|
const offNav = afterNavigate(() => {
|
|
620
|
-
if (mounted)
|
|
682
|
+
if (mounted) refresh();
|
|
621
683
|
});
|
|
622
684
|
return () => {
|
|
623
685
|
mounted = false;
|
|
624
686
|
offNav();
|
|
625
|
-
|
|
687
|
+
bumpShell = null;
|
|
688
|
+
unmountShell?.();
|
|
689
|
+
unmountShell = null;
|
|
626
690
|
headSession?.close();
|
|
627
691
|
headSession = null;
|
|
628
692
|
navChangeCleanup?.();
|
|
@@ -642,7 +706,7 @@ const router = (options = {}) => {
|
|
|
642
706
|
syncFromParts(parsed.pathname, parsed.search, parsed.hash, routes);
|
|
643
707
|
const store = { entries: [] };
|
|
644
708
|
try {
|
|
645
|
-
const page =
|
|
709
|
+
const page = composedPageForPath(parsed.pathname, routes, notFound);
|
|
646
710
|
if (!page) throw new RouteError(404, "Not found");
|
|
647
711
|
let captured;
|
|
648
712
|
const wrapped = async () => {
|
|
@@ -711,8 +775,9 @@ const router = (options = {}) => {
|
|
|
711
775
|
status: result.kind === "error" ? result.status : result.status ?? 200
|
|
712
776
|
});
|
|
713
777
|
},
|
|
714
|
-
route(pattern, page) {
|
|
778
|
+
route(pattern, page, opts = {}) {
|
|
715
779
|
const data = {
|
|
780
|
+
layouts: opts.layouts ?? [],
|
|
716
781
|
page,
|
|
717
782
|
pattern
|
|
718
783
|
};
|
|
@@ -732,4 +797,4 @@ const router = (options = {}) => {
|
|
|
732
797
|
};
|
|
733
798
|
|
|
734
799
|
//#endregion
|
|
735
|
-
export { EMPTY_HEAD, Redirect, RouteError, afterNavigate, beforeNavigate, defineLayout, enableLinkInterception, throwAppError as error, getHistoryMode, head, httpResponse, isActive, navigate, navigating, prime, redirect, resolveRedirectTarget, routeHash, routeParams, routePath, routeSearch, router, serializeHead, setHistoryMode, useContext, useRoute, wrapError, wrapLayout };
|
|
800
|
+
export { EMPTY_HEAD, Redirect, RouteError, afterNavigate, beforeNavigate, defineLayout, enableLinkInterception, throwAppError as error, getHistoryMode, head, httpResponse, isActive, navigate, navigating, prime, redirect, resolveRedirectTarget, routeHash, routeParams, routePath, routeSearch, router, searchParam, serializeHead, setHistoryMode, useContext, useRoute, wrapError, wrapLayout };
|
|
@@ -465,10 +465,13 @@ const pushLayoutErrorImports = (imports, entry, index, importPath) => {
|
|
|
465
465
|
for (const [j, l] of entry.layouts.entries()) imports.push(`import { default as _layout${index}_${j} } from ${JSON.stringify(importPath(l))};`);
|
|
466
466
|
for (const [j, e] of entry.errors.entries()) imports.push(`import { default as _error${index}_${j} } from ${JSON.stringify(importPath(e))};`);
|
|
467
467
|
};
|
|
468
|
-
const pushRegistryAndRoutes = (entry, index, entriesLength, wrappedId, registryLines, routeLines, includeRoutes) => {
|
|
468
|
+
const pushRegistryAndRoutes = (entry, index, entriesLength, wrappedId, registryLines, routeLines, includeRoutes, decomposeLayouts = false) => {
|
|
469
469
|
registryLines.push(` ${JSON.stringify(entry.name)}: ${wrappedId}${index < entriesLength - 1 ? "," : ""}`);
|
|
470
470
|
if (!includeRoutes) return;
|
|
471
|
-
|
|
471
|
+
if (decomposeLayouts && entry.layouts.length > 0) {
|
|
472
|
+
const layouts = entry.layouts.map((_, j) => `_layout${index}_${j}`).join(", ");
|
|
473
|
+
routeLines.push(` .route(${JSON.stringify(entry.pattern)}, _page${index}, { layouts: [${layouts}] })`);
|
|
474
|
+
} else routeLines.push(` .route(${JSON.stringify(entry.pattern)}, ${wrappedId})`);
|
|
472
475
|
if (entry.errors.length > 0) routeLines.push(` .errorBoundary(${JSON.stringify(entry.pattern)}, _error${index}_${entry.errors.length - 1})`);
|
|
473
476
|
};
|
|
474
477
|
const buildServerFile = (entries, serverFile) => {
|
|
@@ -510,7 +513,7 @@ const emitClientEntry = (entry, index, entriesLength, isStatic, clientImport, im
|
|
|
510
513
|
const expr = wrapIslandExpr(`_page${index}`, entry.layouts.length, entry.errors.length, index);
|
|
511
514
|
const wrappedId = `_wrapped${index}`;
|
|
512
515
|
wrappedIslandLines.push(`const ${wrappedId} = ${expr};`);
|
|
513
|
-
pushRegistryAndRoutes(entry, index, entriesLength, wrappedId, registryLines, routeLines, !isStatic);
|
|
516
|
+
pushRegistryAndRoutes(entry, index, entriesLength, wrappedId, registryLines, routeLines, !isStatic, true);
|
|
514
517
|
};
|
|
515
518
|
const buildClientFile = (entries, clientFile, opts) => {
|
|
516
519
|
const { isStatic, interceptLinks } = opts;
|
package/dist/rsbuild.js
CHANGED
package/dist/vite.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilha/router",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.10",
|
|
4
4
|
"description": "A tiny SPA router for Ilha",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontend",
|
|
@@ -69,17 +69,17 @@
|
|
|
69
69
|
"test": "bun test"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"effect": "4.0.0-rc.
|
|
72
|
+
"effect": "4.0.0-rc.118",
|
|
73
73
|
"unplugin": "3.3.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"ilha": "^0.14.
|
|
77
|
-
"oxidejs": "^0.
|
|
76
|
+
"ilha": "^0.14.7",
|
|
77
|
+
"oxidejs": "^0.5.7",
|
|
78
78
|
"vite": "^8.2.2"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"ilha": ">=0.14.
|
|
82
|
-
"oxidejs": ">=0.
|
|
81
|
+
"ilha": ">=0.14.7",
|
|
82
|
+
"oxidejs": ">=0.5.7"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
85
|
"oxidejs": {
|