@pracht/core 0.6.1 → 0.8.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/app-BWriseLj.d.mts +298 -0
- package/dist/app-BvC1uQG5.mjs +308 -0
- package/dist/browser.d.mts +7 -0
- package/dist/browser.mjs +8 -0
- package/dist/client.d.mts +4 -0
- package/dist/client.mjs +4 -0
- package/dist/forwardRef-grZ6t4GS.mjs +27 -0
- package/dist/hydration-M1QzbQ1O.d.mts +23 -0
- package/dist/index.d.mts +7 -438
- package/dist/index.mjs +7 -1965
- package/dist/manifest.d.mts +2 -0
- package/dist/manifest.mjs +2 -0
- package/dist/prefetch-B50kX9RL.mjs +118 -0
- package/dist/prefetch-cache-DzP2Bj9H.mjs +129 -0
- package/dist/prerender-B8_CqYwd.d.mts +51 -0
- package/dist/prerender-D3E2H96p.mjs +648 -0
- package/dist/router-B7J4YYwg.mjs +418 -0
- package/dist/router-BcUmg1kB.d.mts +27 -0
- package/dist/runtime-context-B5pREhcM.mjs +164 -0
- package/dist/runtime-context-ytVd7GmZ.d.mts +72 -0
- package/dist/runtime-middleware-aeBdgjYr.d.mts +77 -0
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +5 -0
- package/dist/types-idmK5omD.mjs +355 -0
- package/package.json +19 -2
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { ComponentChildren, FunctionComponent } from "preact";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Augment this interface to register your app's context type globally.
|
|
6
|
+
* Once registered, all route args (`BaseRouteArgs`, `LoaderArgs`, etc.)
|
|
7
|
+
* will use your context type automatically — no per-file generics needed.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // src/env.d.ts
|
|
11
|
+
* declare module "@pracht/core" {
|
|
12
|
+
* interface Register {
|
|
13
|
+
* context: { env: Env; executionContext: ExecutionContext };
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
interface Register {}
|
|
19
|
+
type RegisteredContext = Register extends {
|
|
20
|
+
context: infer T;
|
|
21
|
+
} ? T : unknown;
|
|
22
|
+
type RenderMode = "spa" | "ssr" | "ssg" | "isg";
|
|
23
|
+
type RouteParams = Record<string, string>;
|
|
24
|
+
type RouteParamInput = string | number | boolean;
|
|
25
|
+
type SearchParamPrimitive = string | number | boolean;
|
|
26
|
+
type SearchParamValue = SearchParamPrimitive | null | undefined | readonly (SearchParamPrimitive | null | undefined)[];
|
|
27
|
+
type SearchParamsInput = string | URLSearchParams | Record<string, SearchParamValue>;
|
|
28
|
+
interface BuildHrefOptions {
|
|
29
|
+
params?: Record<string, RouteParamInput>;
|
|
30
|
+
search?: SearchParamsInput;
|
|
31
|
+
hash?: string;
|
|
32
|
+
}
|
|
33
|
+
interface NavigateOptions {
|
|
34
|
+
replace?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface HrefRouteDefinition {
|
|
37
|
+
id?: string;
|
|
38
|
+
path: string;
|
|
39
|
+
segments?: readonly RouteSegment[];
|
|
40
|
+
}
|
|
41
|
+
type RegisteredRouteMap = Register extends {
|
|
42
|
+
routes: infer TRoutes;
|
|
43
|
+
} ? TRoutes extends Record<string, unknown> ? TRoutes : {} : {};
|
|
44
|
+
type HasRegisteredRoutes = keyof RegisteredRouteMap extends never ? false : true;
|
|
45
|
+
type EmptyRouteParams = Record<never, never>;
|
|
46
|
+
type IsEmptyRouteParams<TParams> = keyof TParams extends never ? true : false;
|
|
47
|
+
type RouteId = HasRegisteredRoutes extends true ? Extract<keyof RegisteredRouteMap, string> : string;
|
|
48
|
+
type RouteParamsFor<TRoute extends RouteId> = HasRegisteredRoutes extends true ? TRoute extends keyof RegisteredRouteMap ? RegisteredRouteMap[TRoute] extends {
|
|
49
|
+
params: infer TParams;
|
|
50
|
+
} ? TParams extends Record<string, unknown> ? TParams : EmptyRouteParams : EmptyRouteParams : never : Record<string, RouteParamInput>;
|
|
51
|
+
type RouteSearchFor<TRoute extends RouteId> = HasRegisteredRoutes extends true ? TRoute extends keyof RegisteredRouteMap ? RegisteredRouteMap[TRoute] extends {
|
|
52
|
+
search: infer TSearch;
|
|
53
|
+
} ? TSearch : SearchParamsInput : never : SearchParamsInput;
|
|
54
|
+
type TypedHrefOptions<TRoute extends RouteId> = IsEmptyRouteParams<RouteParamsFor<TRoute>> extends true ? {
|
|
55
|
+
params?: never;
|
|
56
|
+
search?: RouteSearchFor<TRoute>;
|
|
57
|
+
hash?: string;
|
|
58
|
+
} : {
|
|
59
|
+
params: RouteParamsFor<TRoute>;
|
|
60
|
+
search?: RouteSearchFor<TRoute>;
|
|
61
|
+
hash?: string;
|
|
62
|
+
};
|
|
63
|
+
type HrefOptions<TRoute extends RouteId = RouteId> = HasRegisteredRoutes extends true ? TRoute extends RouteId ? TypedHrefOptions<TRoute> : never : BuildHrefOptions;
|
|
64
|
+
type HrefArgs<TRoute extends RouteId = RouteId> = HasRegisteredRoutes extends true ? TRoute extends RouteId ? IsEmptyRouteParams<RouteParamsFor<TRoute>> extends true ? [options?: TypedHrefOptions<TRoute>] : [options: TypedHrefOptions<TRoute>] : never : [options?: BuildHrefOptions];
|
|
65
|
+
type RouteTarget<TRoute extends RouteId = RouteId> = HasRegisteredRoutes extends true ? TRoute extends RouteId ? {
|
|
66
|
+
route: TRoute;
|
|
67
|
+
} & TypedHrefOptions<TRoute> : never : {
|
|
68
|
+
route: string;
|
|
69
|
+
} & BuildHrefOptions;
|
|
70
|
+
type HrefFn = <TRoute extends RouteId>(route: TRoute, ...args: HrefArgs<TRoute>) => string;
|
|
71
|
+
/**
|
|
72
|
+
* A reference to a module file — either a plain string path or a lazy import
|
|
73
|
+
* function. Using `() => import("./path")` enables IDE click-to-navigate.
|
|
74
|
+
* The vite plugin transforms import functions back to strings at build time.
|
|
75
|
+
*/
|
|
76
|
+
type ModuleRef = string | (() => Promise<any>);
|
|
77
|
+
interface TimeRevalidatePolicy {
|
|
78
|
+
kind: "time";
|
|
79
|
+
seconds: number;
|
|
80
|
+
}
|
|
81
|
+
type RouteRevalidate = TimeRevalidatePolicy;
|
|
82
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
83
|
+
type ApiRouteArgs<TContext = RegisteredContext> = Omit<BaseRouteArgs<TContext>, "route"> & {
|
|
84
|
+
route: ResolvedApiRoute;
|
|
85
|
+
};
|
|
86
|
+
type ApiRouteHandler<TContext = RegisteredContext> = (args: ApiRouteArgs<TContext>) => MaybePromise<Response>;
|
|
87
|
+
interface ApiRouteModule<TContext = any> {
|
|
88
|
+
default?: ApiRouteHandler<TContext>;
|
|
89
|
+
GET?: ApiRouteHandler<TContext>;
|
|
90
|
+
POST?: ApiRouteHandler<TContext>;
|
|
91
|
+
PUT?: ApiRouteHandler<TContext>;
|
|
92
|
+
PATCH?: ApiRouteHandler<TContext>;
|
|
93
|
+
DELETE?: ApiRouteHandler<TContext>;
|
|
94
|
+
HEAD?: ApiRouteHandler<TContext>;
|
|
95
|
+
OPTIONS?: ApiRouteHandler<TContext>;
|
|
96
|
+
}
|
|
97
|
+
interface ResolvedApiRoute {
|
|
98
|
+
path: string;
|
|
99
|
+
file: string;
|
|
100
|
+
segments: RouteSegment[];
|
|
101
|
+
}
|
|
102
|
+
interface ApiRouteMatch {
|
|
103
|
+
route: ResolvedApiRoute;
|
|
104
|
+
params: RouteParams;
|
|
105
|
+
pathname: string;
|
|
106
|
+
}
|
|
107
|
+
type PrefetchStrategy = "none" | "hover" | "viewport" | "intent";
|
|
108
|
+
interface RouteMeta {
|
|
109
|
+
id?: string;
|
|
110
|
+
shell?: string;
|
|
111
|
+
render?: RenderMode;
|
|
112
|
+
middleware?: string[];
|
|
113
|
+
revalidate?: RouteRevalidate;
|
|
114
|
+
prefetch?: PrefetchStrategy;
|
|
115
|
+
hasLoader?: boolean;
|
|
116
|
+
}
|
|
117
|
+
interface GroupMeta {
|
|
118
|
+
shell?: string;
|
|
119
|
+
render?: RenderMode;
|
|
120
|
+
middleware?: string[];
|
|
121
|
+
pathPrefix?: string;
|
|
122
|
+
}
|
|
123
|
+
interface ApiConfig {
|
|
124
|
+
middleware?: string[];
|
|
125
|
+
/**
|
|
126
|
+
* When `true` (the default), state-changing API requests
|
|
127
|
+
* (POST/PUT/PATCH/DELETE) are rejected unless the browser signals an
|
|
128
|
+
* exact same-origin fetch (`Sec-Fetch-Site: same-origin`) or the request
|
|
129
|
+
* Origin/Referer matches the request URL's origin. `same-site` is not
|
|
130
|
+
* accepted by default because sibling subdomains can be attacker-controlled.
|
|
131
|
+
* Set to `false` to opt out if you build your own CSRF protection into middleware.
|
|
132
|
+
*/
|
|
133
|
+
requireSameOrigin?: boolean;
|
|
134
|
+
}
|
|
135
|
+
interface RouteConfig extends RouteMeta {
|
|
136
|
+
component: ModuleRef;
|
|
137
|
+
loader?: ModuleRef;
|
|
138
|
+
}
|
|
139
|
+
interface RouteDefinition extends RouteMeta {
|
|
140
|
+
kind: "route";
|
|
141
|
+
path: string;
|
|
142
|
+
file: string;
|
|
143
|
+
loaderFile?: string;
|
|
144
|
+
}
|
|
145
|
+
interface GroupDefinition {
|
|
146
|
+
kind: "group";
|
|
147
|
+
meta: GroupMeta;
|
|
148
|
+
routes: RouteTreeNode[];
|
|
149
|
+
}
|
|
150
|
+
type RouteTreeNode = RouteDefinition | GroupDefinition;
|
|
151
|
+
interface PrachtAppConfig {
|
|
152
|
+
shells?: Record<string, ModuleRef>;
|
|
153
|
+
middleware?: Record<string, ModuleRef>;
|
|
154
|
+
api?: ApiConfig;
|
|
155
|
+
routes: RouteTreeNode[];
|
|
156
|
+
}
|
|
157
|
+
interface PrachtApp {
|
|
158
|
+
shells: Record<string, string>;
|
|
159
|
+
middleware: Record<string, string>;
|
|
160
|
+
api: ApiConfig;
|
|
161
|
+
routes: RouteTreeNode[];
|
|
162
|
+
}
|
|
163
|
+
interface StaticRouteSegment {
|
|
164
|
+
type: "static";
|
|
165
|
+
value: string;
|
|
166
|
+
}
|
|
167
|
+
interface ParamRouteSegment {
|
|
168
|
+
type: "param";
|
|
169
|
+
name: string;
|
|
170
|
+
}
|
|
171
|
+
interface CatchAllRouteSegment {
|
|
172
|
+
type: "catchall";
|
|
173
|
+
name: string;
|
|
174
|
+
}
|
|
175
|
+
type RouteSegment = StaticRouteSegment | ParamRouteSegment | CatchAllRouteSegment;
|
|
176
|
+
interface ResolvedRoute extends Omit<RouteMeta, "middleware"> {
|
|
177
|
+
path: string;
|
|
178
|
+
file: string;
|
|
179
|
+
loaderFile?: string;
|
|
180
|
+
shell?: string;
|
|
181
|
+
shellFile?: string;
|
|
182
|
+
middleware: string[];
|
|
183
|
+
middlewareFiles: string[];
|
|
184
|
+
segments: RouteSegment[];
|
|
185
|
+
}
|
|
186
|
+
interface ResolvedPrachtApp extends Omit<PrachtApp, "routes"> {
|
|
187
|
+
routes: ResolvedRoute[];
|
|
188
|
+
apiRoutes: ResolvedApiRoute[];
|
|
189
|
+
}
|
|
190
|
+
interface RouteMatch {
|
|
191
|
+
route: ResolvedRoute;
|
|
192
|
+
params: RouteParams;
|
|
193
|
+
pathname: string;
|
|
194
|
+
}
|
|
195
|
+
interface BaseRouteArgs<TContext = RegisteredContext> {
|
|
196
|
+
request: Request;
|
|
197
|
+
params: RouteParams;
|
|
198
|
+
context: TContext;
|
|
199
|
+
signal: AbortSignal;
|
|
200
|
+
url: URL;
|
|
201
|
+
route: ResolvedRoute;
|
|
202
|
+
}
|
|
203
|
+
interface LoaderArgs<TContext = RegisteredContext> extends BaseRouteArgs<TContext> {}
|
|
204
|
+
interface MiddlewareArgs<TContext = RegisteredContext> extends BaseRouteArgs<TContext> {}
|
|
205
|
+
type HeadAttributes = Record<string, string | undefined>;
|
|
206
|
+
interface HeadScriptDescriptor extends HeadAttributes {
|
|
207
|
+
children?: string;
|
|
208
|
+
}
|
|
209
|
+
interface HeadMetadata {
|
|
210
|
+
title?: string;
|
|
211
|
+
lang?: string;
|
|
212
|
+
meta?: HeadAttributes[];
|
|
213
|
+
link?: HeadAttributes[];
|
|
214
|
+
script?: HeadScriptDescriptor[];
|
|
215
|
+
}
|
|
216
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
217
|
+
type LoaderLike = ((args: LoaderArgs<any>) => unknown) | undefined;
|
|
218
|
+
type LoaderData<TLoader extends LoaderLike> = TLoader extends ((...args: any[]) => infer TResult) ? Awaited<TResult> : never;
|
|
219
|
+
interface HeadArgs<TLoader extends LoaderLike = undefined, TContext = any> extends BaseRouteArgs<TContext> {
|
|
220
|
+
data: LoaderData<TLoader>;
|
|
221
|
+
}
|
|
222
|
+
interface HeadersArgs<TLoader extends LoaderLike = undefined, TContext = any> extends BaseRouteArgs<TContext> {
|
|
223
|
+
data: LoaderData<TLoader>;
|
|
224
|
+
}
|
|
225
|
+
interface RouteComponentProps<TLoader extends LoaderLike = undefined> {
|
|
226
|
+
data: LoaderData<TLoader>;
|
|
227
|
+
params: RouteParams;
|
|
228
|
+
}
|
|
229
|
+
interface ErrorBoundaryProps {
|
|
230
|
+
error: Error & {
|
|
231
|
+
diagnostics?: unknown;
|
|
232
|
+
status?: number;
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
interface ShellProps {
|
|
236
|
+
children: ComponentChildren;
|
|
237
|
+
}
|
|
238
|
+
type LoaderFn<TContext = any, TData = unknown> = (args: LoaderArgs<TContext>) => MaybePromise<TData>;
|
|
239
|
+
interface RouteModule<TContext = any, TLoader extends LoaderLike = undefined> {
|
|
240
|
+
loader?: LoaderFn<TContext>;
|
|
241
|
+
head?: (args: HeadArgs<TLoader, TContext>) => MaybePromise<HeadMetadata>;
|
|
242
|
+
headers?: (args: HeadersArgs<TLoader, TContext>) => MaybePromise<HeadersInit>;
|
|
243
|
+
Component?: FunctionComponent<RouteComponentProps<TLoader>>;
|
|
244
|
+
default?: FunctionComponent<RouteComponentProps<TLoader>>;
|
|
245
|
+
ErrorBoundary?: FunctionComponent<ErrorBoundaryProps>;
|
|
246
|
+
getStaticPaths?: () => MaybePromise<RouteParams[]>;
|
|
247
|
+
markdown?: string;
|
|
248
|
+
}
|
|
249
|
+
interface ShellModule<TContext = any> {
|
|
250
|
+
Shell: FunctionComponent<ShellProps>;
|
|
251
|
+
Loading?: FunctionComponent;
|
|
252
|
+
ErrorBoundary?: FunctionComponent<ErrorBoundaryProps>;
|
|
253
|
+
head?: (args: BaseRouteArgs<TContext>) => MaybePromise<HeadMetadata>;
|
|
254
|
+
headers?: (args: BaseRouteArgs<TContext>) => MaybePromise<HeadersInit>;
|
|
255
|
+
}
|
|
256
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
257
|
+
type MiddlewareFn<TContext = any> = (args: MiddlewareArgs<TContext>, next: MiddlewareNext) => MaybePromise<Response>;
|
|
258
|
+
interface MiddlewareModule<TContext = any> {
|
|
259
|
+
middleware: MiddlewareFn<TContext>;
|
|
260
|
+
}
|
|
261
|
+
type ModuleImporter<TModule = unknown> = () => Promise<TModule>;
|
|
262
|
+
interface DataModule<TContext = any> {
|
|
263
|
+
loader?: LoaderFn<TContext>;
|
|
264
|
+
}
|
|
265
|
+
interface ModuleRegistry {
|
|
266
|
+
routeModules?: Record<string, ModuleImporter<RouteModule>>;
|
|
267
|
+
shellModules?: Record<string, ModuleImporter<ShellModule>>;
|
|
268
|
+
middlewareModules?: Record<string, ModuleImporter<MiddlewareModule>>;
|
|
269
|
+
apiModules?: Record<string, ModuleImporter<ApiRouteModule>>;
|
|
270
|
+
dataModules?: Record<string, ModuleImporter<DataModule>>;
|
|
271
|
+
}
|
|
272
|
+
declare class PrachtHttpError extends Error {
|
|
273
|
+
readonly status: number;
|
|
274
|
+
constructor(status: number, message: string);
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/app.d.ts
|
|
278
|
+
declare function timeRevalidate(seconds: number): TimeRevalidatePolicy;
|
|
279
|
+
declare function route(path: string, file: ModuleRef, meta?: RouteMeta): RouteDefinition;
|
|
280
|
+
declare function route(path: string, config: RouteConfig): RouteDefinition;
|
|
281
|
+
declare function group(meta: GroupMeta, routes: RouteTreeNode[]): GroupDefinition;
|
|
282
|
+
declare function defineApp(config: PrachtAppConfig): PrachtApp;
|
|
283
|
+
declare function resolveApp(app: PrachtApp): ResolvedPrachtApp;
|
|
284
|
+
declare function matchAppRoute(app: PrachtApp | ResolvedPrachtApp, pathname: string): RouteMatch | undefined;
|
|
285
|
+
declare function buildPathFromSegments(segments: readonly RouteSegment[], params: RouteParams): string;
|
|
286
|
+
declare function buildHref<TRoute extends RouteId>(routes: readonly HrefRouteDefinition[], routeId: TRoute, ...args: HrefArgs<TRoute>): string;
|
|
287
|
+
/**
|
|
288
|
+
* Convert a list of file paths from `import.meta.glob` into resolved API routes.
|
|
289
|
+
*
|
|
290
|
+
* Example: `"/src/api/health.ts"` → path `/api/health`
|
|
291
|
+
* `"/src/api/users/[id].ts"` → path `/api/users/:id`
|
|
292
|
+
* `"/src/api/files/[...path].ts"` → path `/api/files/*`
|
|
293
|
+
* `"/src/api/index.ts"` → path `/api`
|
|
294
|
+
*/
|
|
295
|
+
declare function resolveApiRoutes(files: string[], apiDir?: string): ResolvedApiRoute[];
|
|
296
|
+
declare function matchApiRoute(apiRoutes: ResolvedApiRoute[], pathname: string): ApiRouteMatch | undefined;
|
|
297
|
+
//#endregion
|
|
298
|
+
export { RouteComponentProps as $, HrefRouteDefinition as A, ModuleImporter as B, HeadAttributes as C, HrefArgs as D, HeadersArgs as E, LoaderLike as F, PrachtAppConfig as G, ModuleRegistry as H, MiddlewareArgs as I, Register as J, PrachtHttpError as K, MiddlewareFn as L, LoaderArgs as M, LoaderData as N, HrefFn as O, LoaderFn as P, ResolvedRoute as Q, MiddlewareModule as R, HeadArgs as S, HeadScriptDescriptor as T, NavigateOptions as U, ModuleRef as V, PrachtApp as W, ResolvedApiRoute as X, RenderMode as Y, ResolvedPrachtApp as Z, BuildHrefOptions as _, ShellProps as _t, matchApiRoute as a, RouteModule as at, GroupDefinition as b, resolveApp as c, RouteParamsFor as ct, ApiConfig as d, RouteTarget as dt, RouteConfig as et, ApiRouteArgs as f, RouteTreeNode as ft, BaseRouteArgs as g, ShellModule as gt, ApiRouteModule as h, SearchParamsInput as ht, group as i, RouteMeta as it, HttpMethod as j, HrefOptions as k, route as l, RouteRevalidate as lt, ApiRouteMatch as m, SearchParamValue as mt, buildPathFromSegments as n, RouteId as nt, matchAppRoute as o, RouteParamInput as ot, ApiRouteHandler as p, SearchParamPrimitive as pt, PrefetchStrategy as q, defineApp as r, RouteMatch as rt, resolveApiRoutes as s, RouteParams as st, buildHref as t, RouteDefinition as tt, timeRevalidate as u, RouteSearchFor as ut, DataModule as v, TimeRevalidatePolicy as vt, HeadMetadata as w, GroupMeta as x, ErrorBoundaryProps as y, MiddlewareNext as z };
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
//#region src/app.ts
|
|
2
|
+
function timeRevalidate(seconds) {
|
|
3
|
+
if (!Number.isInteger(seconds) || seconds <= 0) throw new Error("timeRevalidate expects a positive integer number of seconds.");
|
|
4
|
+
return {
|
|
5
|
+
kind: "time",
|
|
6
|
+
seconds
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function route(path, fileOrConfig, meta = {}) {
|
|
10
|
+
if (typeof fileOrConfig === "string" || typeof fileOrConfig === "function") return {
|
|
11
|
+
kind: "route",
|
|
12
|
+
path: normalizeRoutePath(path),
|
|
13
|
+
file: resolveModuleRef(fileOrConfig),
|
|
14
|
+
...meta
|
|
15
|
+
};
|
|
16
|
+
const { component, loader, ...routeMeta } = fileOrConfig;
|
|
17
|
+
return {
|
|
18
|
+
kind: "route",
|
|
19
|
+
path: normalizeRoutePath(path),
|
|
20
|
+
file: resolveModuleRef(component),
|
|
21
|
+
loaderFile: resolveModuleRef(loader),
|
|
22
|
+
hasLoader: !!loader,
|
|
23
|
+
...routeMeta
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function resolveModuleRef(ref) {
|
|
27
|
+
if (ref === void 0) return void 0;
|
|
28
|
+
if (typeof ref === "string") return ref;
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
function group(meta, routes) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "group",
|
|
34
|
+
meta,
|
|
35
|
+
routes
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function defineApp(config) {
|
|
39
|
+
return {
|
|
40
|
+
shells: resolveModuleRefRecord(config.shells ?? {}),
|
|
41
|
+
middleware: resolveModuleRefRecord(config.middleware ?? {}),
|
|
42
|
+
api: config.api ?? {},
|
|
43
|
+
routes: config.routes
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function resolveModuleRefRecord(record) {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [key, value] of Object.entries(record)) result[key] = resolveModuleRef(value);
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function resolveApp(app) {
|
|
52
|
+
const routes = [];
|
|
53
|
+
const inherited = {
|
|
54
|
+
pathPrefix: "/",
|
|
55
|
+
middleware: []
|
|
56
|
+
};
|
|
57
|
+
for (const node of app.routes) flattenRouteNode(app, node, inherited, routes);
|
|
58
|
+
return {
|
|
59
|
+
shells: app.shells,
|
|
60
|
+
middleware: app.middleware,
|
|
61
|
+
api: app.api,
|
|
62
|
+
routes,
|
|
63
|
+
apiRoutes: []
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function matchAppRoute(app, pathname) {
|
|
67
|
+
const resolved = isResolvedApp(app) ? app : resolveApp(app);
|
|
68
|
+
const normalizedPathname = normalizeRoutePath(pathname);
|
|
69
|
+
const targetSegments = splitPathSegments(normalizedPathname);
|
|
70
|
+
for (const currentRoute of resolved.routes) {
|
|
71
|
+
const params = matchRouteSegments(currentRoute.segments, targetSegments);
|
|
72
|
+
if (params) return {
|
|
73
|
+
route: currentRoute,
|
|
74
|
+
params,
|
|
75
|
+
pathname: normalizedPathname
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function flattenRouteNode(app, node, inherited, routes) {
|
|
80
|
+
if (node.kind === "group") {
|
|
81
|
+
const nextInherited = {
|
|
82
|
+
pathPrefix: mergeRoutePaths(inherited.pathPrefix, node.meta.pathPrefix),
|
|
83
|
+
shell: node.meta.shell ?? inherited.shell,
|
|
84
|
+
render: node.meta.render ?? inherited.render,
|
|
85
|
+
middleware: [...inherited.middleware, ...node.meta.middleware ?? []]
|
|
86
|
+
};
|
|
87
|
+
for (const child of node.routes) flattenRouteNode(app, child, nextInherited, routes);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fullPath = mergeRoutePaths(inherited.pathPrefix, node.path);
|
|
91
|
+
const shell = node.shell ?? inherited.shell;
|
|
92
|
+
const middleware = [...inherited.middleware, ...node.middleware ?? []];
|
|
93
|
+
routes.push({
|
|
94
|
+
id: node.id ?? createRouteId(fullPath),
|
|
95
|
+
path: fullPath,
|
|
96
|
+
file: node.file,
|
|
97
|
+
loaderFile: node.loaderFile,
|
|
98
|
+
hasLoader: node.loaderFile ? true : node.hasLoader,
|
|
99
|
+
shell,
|
|
100
|
+
shellFile: shell ? app.shells[shell] : void 0,
|
|
101
|
+
render: node.render ?? inherited.render,
|
|
102
|
+
middleware,
|
|
103
|
+
middlewareFiles: middleware.flatMap((name) => {
|
|
104
|
+
const middlewareFile = app.middleware[name];
|
|
105
|
+
return middlewareFile ? [middlewareFile] : [];
|
|
106
|
+
}),
|
|
107
|
+
revalidate: node.revalidate,
|
|
108
|
+
segments: parseRouteSegments(fullPath)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function isResolvedApp(app) {
|
|
112
|
+
return app.routes.length === 0 || "segments" in app.routes[0];
|
|
113
|
+
}
|
|
114
|
+
function matchRouteSegments(routeSegments, targetSegments) {
|
|
115
|
+
const params = {};
|
|
116
|
+
let routeIndex = 0;
|
|
117
|
+
let targetIndex = 0;
|
|
118
|
+
while (routeIndex < routeSegments.length) {
|
|
119
|
+
const currentSegment = routeSegments[routeIndex];
|
|
120
|
+
if (currentSegment.type === "catchall") {
|
|
121
|
+
try {
|
|
122
|
+
params[currentSegment.name] = targetSegments.slice(targetIndex).map(decodeURIComponent).join("/");
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return params;
|
|
127
|
+
}
|
|
128
|
+
const targetSegment = targetSegments[targetIndex];
|
|
129
|
+
if (typeof targetSegment === "undefined") return null;
|
|
130
|
+
if (currentSegment.type === "static") {
|
|
131
|
+
if (currentSegment.value !== targetSegment) return null;
|
|
132
|
+
} else try {
|
|
133
|
+
params[currentSegment.name] = decodeURIComponent(targetSegment);
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
routeIndex += 1;
|
|
138
|
+
targetIndex += 1;
|
|
139
|
+
}
|
|
140
|
+
return targetIndex === targetSegments.length ? params : null;
|
|
141
|
+
}
|
|
142
|
+
function parseRouteSegments(path) {
|
|
143
|
+
return splitPathSegments(path).map((segment) => {
|
|
144
|
+
if (segment === "*") return {
|
|
145
|
+
type: "catchall",
|
|
146
|
+
name: "*"
|
|
147
|
+
};
|
|
148
|
+
if (segment.startsWith(":") && segment.endsWith("*")) return {
|
|
149
|
+
type: "catchall",
|
|
150
|
+
name: segment.slice(1, -1) || "*"
|
|
151
|
+
};
|
|
152
|
+
if (segment.startsWith(":")) return {
|
|
153
|
+
type: "param",
|
|
154
|
+
name: segment.slice(1)
|
|
155
|
+
};
|
|
156
|
+
assertSafeStaticRouteSegment(segment);
|
|
157
|
+
return {
|
|
158
|
+
type: "static",
|
|
159
|
+
value: segment
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function splitPathSegments(path) {
|
|
164
|
+
return normalizeRoutePath(path).split("/").filter(Boolean);
|
|
165
|
+
}
|
|
166
|
+
function assertSafeStaticRouteSegment(segment) {
|
|
167
|
+
if (segment === "." || segment === "..") throw new Error(`Unsafe static route segment "${segment}" is not allowed.`);
|
|
168
|
+
if (segment.includes("\0") || /[\r\n\\]/.test(segment)) throw new Error(`Unsafe static route segment "${segment}" contains a forbidden character.`);
|
|
169
|
+
}
|
|
170
|
+
function mergeRoutePaths(prefix, path) {
|
|
171
|
+
if (!path) return normalizeRoutePath(prefix);
|
|
172
|
+
const normalizedPrefix = normalizeRoutePath(prefix);
|
|
173
|
+
const normalizedPath = normalizeRoutePath(path);
|
|
174
|
+
if (normalizedPrefix === "/") return normalizedPath;
|
|
175
|
+
if (normalizedPath === "/") return normalizedPrefix;
|
|
176
|
+
return normalizeRoutePath(`${normalizedPrefix}/${normalizedPath.slice(1)}`);
|
|
177
|
+
}
|
|
178
|
+
function normalizeRoutePath(path) {
|
|
179
|
+
if (!path || path === "/") return "/";
|
|
180
|
+
const collapsed = (path.startsWith("/") ? path : `/${path}`).replace(/\/{2,}/g, "/");
|
|
181
|
+
return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
182
|
+
}
|
|
183
|
+
function buildPathFromSegments(segments, params) {
|
|
184
|
+
return normalizeRoutePath("/" + segments.map((segment) => {
|
|
185
|
+
if (segment.type === "static") return segment.value;
|
|
186
|
+
if (segment.type === "param") return encodeDynamicPathSegment(params[segment.name] ?? "");
|
|
187
|
+
return (params[segment.name] ?? params["*"] ?? "").split("/").map((part) => encodeDynamicPathSegment(part)).join("/");
|
|
188
|
+
}).join("/"));
|
|
189
|
+
}
|
|
190
|
+
function buildHref(routes, routeId, ...args) {
|
|
191
|
+
return buildHrefUntyped(routes, String(routeId), args[0]);
|
|
192
|
+
}
|
|
193
|
+
function buildHrefUntyped(routes, routeId, options = {}) {
|
|
194
|
+
const route = routes.find((candidate) => candidate.id === routeId);
|
|
195
|
+
if (!route) throw new Error(`Unknown pracht route id: ${routeId}.`);
|
|
196
|
+
const segments = route.segments ?? parseRouteSegments(route.path);
|
|
197
|
+
return `${buildPathFromSegments(segments, normalizeHrefParams(segments, options.params ?? {}))}${serializeSearch(options.search)}${serializeHash(options.hash)}`;
|
|
198
|
+
}
|
|
199
|
+
function normalizeHrefParams(segments, params) {
|
|
200
|
+
const expected = new Set(segments.filter((segment) => segment.type === "param" || segment.type === "catchall").map((segment) => segment.name));
|
|
201
|
+
for (const name of expected) if (params[name] == null) throw new Error(`Missing route param: ${name}.`);
|
|
202
|
+
for (const name of Object.keys(params)) if (!expected.has(name)) throw new Error(`Unexpected route param: ${name}.`);
|
|
203
|
+
const normalized = {};
|
|
204
|
+
for (const name of expected) normalized[name] = String(params[name]);
|
|
205
|
+
return normalized;
|
|
206
|
+
}
|
|
207
|
+
function serializeSearch(search) {
|
|
208
|
+
if (search == null) return "";
|
|
209
|
+
if (typeof search === "string") {
|
|
210
|
+
if (!search) return "";
|
|
211
|
+
return search.startsWith("?") ? search : `?${search}`;
|
|
212
|
+
}
|
|
213
|
+
const serialized = (search instanceof URLSearchParams ? search : objectToSearchParams(search)).toString();
|
|
214
|
+
return serialized ? `?${serialized}` : "";
|
|
215
|
+
}
|
|
216
|
+
function objectToSearchParams(search) {
|
|
217
|
+
const params = new URLSearchParams();
|
|
218
|
+
for (const [key, value] of Object.entries(search)) {
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
for (const item of value) appendSearchValue(params, key, item);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
appendSearchValue(params, key, value);
|
|
224
|
+
}
|
|
225
|
+
return params;
|
|
226
|
+
}
|
|
227
|
+
function appendSearchValue(params, key, value) {
|
|
228
|
+
if (value == null) return;
|
|
229
|
+
params.append(key, String(value));
|
|
230
|
+
}
|
|
231
|
+
function serializeHash(hash) {
|
|
232
|
+
if (!hash) return "";
|
|
233
|
+
return hash.startsWith("#") ? hash : `#${hash}`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Encode one dynamic URL path segment for SSG/ISG output. `encodeURIComponent`
|
|
237
|
+
* leaves unreserved characters (including `.`) intact, and even percent-encoded
|
|
238
|
+
* dot segments are normalized by URL parsers. Reject exact `.` / `..` segments
|
|
239
|
+
* instead of allowing them to reach filesystem output path construction.
|
|
240
|
+
*/
|
|
241
|
+
function encodeDynamicPathSegment(part) {
|
|
242
|
+
if (part === "." || part === "..") throw new Error(`Unsafe dynamic route param segment "${part}" is not allowed.`);
|
|
243
|
+
return encodeURIComponent(part);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Convert a list of file paths from `import.meta.glob` into resolved API routes.
|
|
247
|
+
*
|
|
248
|
+
* Example: `"/src/api/health.ts"` → path `/api/health`
|
|
249
|
+
* `"/src/api/users/[id].ts"` → path `/api/users/:id`
|
|
250
|
+
* `"/src/api/files/[...path].ts"` → path `/api/files/*`
|
|
251
|
+
* `"/src/api/index.ts"` → path `/api`
|
|
252
|
+
*/
|
|
253
|
+
function resolveApiRoutes(files, apiDir = "/src/api") {
|
|
254
|
+
const normalizedDir = apiDir.replace(/\/$/, "");
|
|
255
|
+
return files.map((file) => {
|
|
256
|
+
let relative = file;
|
|
257
|
+
if (relative.startsWith(normalizedDir)) relative = relative.slice(normalizedDir.length);
|
|
258
|
+
relative = relative.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
259
|
+
if (relative.endsWith("/index")) relative = relative.slice(0, -6) || "/";
|
|
260
|
+
relative = relative.replace(/\[\.\.\.[^\]]+\]/g, "*");
|
|
261
|
+
relative = relative.replace(/\[([^\]]+)\]/g, ":$1");
|
|
262
|
+
const path = normalizeRoutePath(`/api${relative}`);
|
|
263
|
+
return {
|
|
264
|
+
path,
|
|
265
|
+
file,
|
|
266
|
+
segments: parseRouteSegments(path)
|
|
267
|
+
};
|
|
268
|
+
}).sort(compareResolvedApiRoutes);
|
|
269
|
+
}
|
|
270
|
+
function matchApiRoute(apiRoutes, pathname) {
|
|
271
|
+
const normalizedPathname = normalizeRoutePath(pathname);
|
|
272
|
+
const targetSegments = splitPathSegments(normalizedPathname);
|
|
273
|
+
for (const route of apiRoutes) {
|
|
274
|
+
const params = matchRouteSegments(route.segments, targetSegments);
|
|
275
|
+
if (params) return {
|
|
276
|
+
route,
|
|
277
|
+
params,
|
|
278
|
+
pathname: normalizedPathname
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function createRouteId(path) {
|
|
283
|
+
if (path === "/") return "index";
|
|
284
|
+
return path.slice(1).split("/").map((segment) => {
|
|
285
|
+
if (segment === "*") return "splat";
|
|
286
|
+
return segment.startsWith(":") ? segment.slice(1) : segment;
|
|
287
|
+
}).join("-").replace(/[^a-zA-Z0-9-]/g, "-");
|
|
288
|
+
}
|
|
289
|
+
function compareResolvedApiRoutes(left, right) {
|
|
290
|
+
const length = Math.max(left.segments.length, right.segments.length);
|
|
291
|
+
for (let index = 0; index < length; index += 1) {
|
|
292
|
+
const leftSegment = left.segments[index];
|
|
293
|
+
const rightSegment = right.segments[index];
|
|
294
|
+
if (!leftSegment) return 1;
|
|
295
|
+
if (!rightSegment) return -1;
|
|
296
|
+
const leftScore = getRouteSegmentSpecificity(leftSegment);
|
|
297
|
+
const rightScore = getRouteSegmentSpecificity(rightSegment);
|
|
298
|
+
if (leftScore !== rightScore) return rightScore - leftScore;
|
|
299
|
+
}
|
|
300
|
+
return left.path.localeCompare(right.path);
|
|
301
|
+
}
|
|
302
|
+
function getRouteSegmentSpecificity(segment) {
|
|
303
|
+
if (segment.type === "static") return 3;
|
|
304
|
+
if (segment.type === "param") return 2;
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
export { matchApiRoute as a, resolveApp as c, group as i, route as l, buildPathFromSegments as n, matchAppRoute as o, defineApp as r, resolveApiRoutes as s, buildHref as t, timeRevalidate as u };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { $ as RouteComponentProps, A as HrefRouteDefinition, B as ModuleImporter, C as HeadAttributes, D as HrefArgs, E as HeadersArgs, G as PrachtAppConfig, H as ModuleRegistry, I as MiddlewareArgs, J as Register, K as PrachtHttpError, L as MiddlewareFn, M as LoaderArgs, N as LoaderData, O as HrefFn, P as LoaderFn, Q as ResolvedRoute, R as MiddlewareModule, S as HeadArgs, T as HeadScriptDescriptor, U as NavigateOptions, V as ModuleRef, W as PrachtApp, X as ResolvedApiRoute, Y as RenderMode, Z as ResolvedPrachtApp, _ as BuildHrefOptions, _t as ShellProps, at as RouteModule, b as GroupDefinition, c as resolveApp, ct as RouteParamsFor, d as ApiConfig, dt as RouteTarget, et as RouteConfig, f as ApiRouteArgs, ft as RouteTreeNode, g as BaseRouteArgs, gt as ShellModule, h as ApiRouteModule, ht as SearchParamsInput, i as group, it as RouteMeta, j as HttpMethod, k as HrefOptions, l as route, lt as RouteRevalidate, m as ApiRouteMatch, mt as SearchParamValue, n as buildPathFromSegments, nt as RouteId, o as matchAppRoute, ot as RouteParamInput, p as ApiRouteHandler, pt as SearchParamPrimitive, q as PrefetchStrategy, r as defineApp, rt as RouteMatch, st as RouteParams, t as buildHref, tt as RouteDefinition, u as timeRevalidate, ut as RouteSearchFor, v as DataModule, vt as TimeRevalidatePolicy, w as HeadMetadata, x as GroupMeta, y as ErrorBoundaryProps, z as MiddlewareNext } from "./app-BWriseLj.mjs";
|
|
2
|
+
import { a as parseSafeNavigationUrl, c as Link, d as useLocation, f as useParams, h as createHref, i as fetchPrachtRouteState, l as LinkProps, m as useRouteData, n as redirect, o as Form, p as useRevalidate, r as RouteStateResult, s as FormProps, t as RedirectOptions, u as Location } from "./runtime-middleware-aeBdgjYr.mjs";
|
|
3
|
+
import { n as forwardRef, t as useIsHydrated } from "./hydration-M1QzbQ1O.mjs";
|
|
4
|
+
import { a as startApp, c as SerializedRouteError, i as readHydrationState, n as PrachtRuntimeProvider, r as StartAppOptions, t as PrachtHydrationState } from "./runtime-context-ytVd7GmZ.mjs";
|
|
5
|
+
import { i as useNavigate, n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-BcUmg1kB.mjs";
|
|
6
|
+
import { Suspense, lazy } from "preact-suspense";
|
|
7
|
+
export { type ApiConfig, type ApiRouteArgs, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type BuildHrefOptions, type DataModule, type ErrorBoundaryProps, Form, type FormProps, type GroupDefinition, type GroupMeta, type HeadArgs, type HeadAttributes, type HeadMetadata, type HeadScriptDescriptor, type HeadersArgs, type HrefArgs, type HrefFn, type HrefOptions, type HrefRouteDefinition, type HttpMethod, type InitClientRouterOptions, Link, type LinkProps, type LoaderArgs, type LoaderData, type LoaderFn, type Location, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareNext, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateFn, type NavigateOptions, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtHydrationState, PrachtRuntimeProvider, type PrefetchStrategy, type RedirectOptions, type Register, type RenderMode, type ResolvedApiRoute, type ResolvedPrachtApp, type ResolvedRoute, type RouteComponentProps, type RouteConfig, type RouteDefinition, type RouteId, type RouteMatch, type RouteMeta, type RouteModule, type RouteParamInput, type RouteParams, type RouteParamsFor, type RouteRevalidate, type RouteSearchFor, type RouteStateResult, type RouteTarget, type RouteTreeNode, type SearchParamPrimitive, type SearchParamValue, type SearchParamsInput, type SerializedRouteError, type ShellModule, type ShellProps, type StartAppOptions, Suspense, type TimeRevalidatePolicy, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRouteData };
|
package/dist/browser.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, t as buildHref, u as timeRevalidate } from "./app-BvC1uQG5.mjs";
|
|
2
|
+
import { S as createHref, a as useParams, i as useLocation, n as Form, o as useRevalidate, r as Link, s as useRouteData, t as PrachtHttpError, u as redirect } from "./types-idmK5omD.mjs";
|
|
3
|
+
import { t as forwardRef } from "./forwardRef-grZ6t4GS.mjs";
|
|
4
|
+
import { n as useNavigate, r as useIsHydrated, t as initClientRouter } from "./router-B7J4YYwg.mjs";
|
|
5
|
+
import { l as parseSafeNavigationUrl, s as fetchPrachtRouteState } from "./prefetch-cache-DzP2Bj9H.mjs";
|
|
6
|
+
import { i as startApp, r as readHydrationState, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
|
|
7
|
+
import { Suspense, lazy } from "preact-suspense";
|
|
8
|
+
export { Form, Link, PrachtHttpError, PrachtRuntimeProvider, Suspense, buildHref, buildPathFromSegments, createHref, defineApp, fetchPrachtRouteState, forwardRef, group, initClientRouter, lazy, matchAppRoute, parseSafeNavigationUrl, readHydrationState, redirect, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRouteData };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { c as resolveApp } from "./app-BWriseLj.mjs";
|
|
2
|
+
import { i as readHydrationState, t as PrachtHydrationState } from "./runtime-context-ytVd7GmZ.mjs";
|
|
3
|
+
import { n as NavigateFn, r as initClientRouter, t as InitClientRouterOptions } from "./router-BcUmg1kB.mjs";
|
|
4
|
+
export { type InitClientRouterOptions, type NavigateFn, type PrachtHydrationState, initClientRouter, readHydrationState, resolveApp };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { options } from "preact";
|
|
2
|
+
//#region src/forwardRef.ts
|
|
3
|
+
let oldDiffHook = options.__b;
|
|
4
|
+
options.__b = (vnode) => {
|
|
5
|
+
if (vnode.type && vnode.type.__f && vnode.ref) {
|
|
6
|
+
vnode.props.ref = vnode.ref;
|
|
7
|
+
vnode.ref = null;
|
|
8
|
+
}
|
|
9
|
+
if (oldDiffHook) oldDiffHook(vnode);
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Pass ref down to a child. This is mainly used in libraries with HOCs that
|
|
13
|
+
* wrap components. Using `forwardRef` there is an easy way to get a reference
|
|
14
|
+
* of the wrapped component instead of one of the wrapper itself.
|
|
15
|
+
*/
|
|
16
|
+
function forwardRef(fn) {
|
|
17
|
+
function Forwarded(props) {
|
|
18
|
+
const clone = { ...props };
|
|
19
|
+
delete clone.ref;
|
|
20
|
+
return fn(clone, props.ref || null);
|
|
21
|
+
}
|
|
22
|
+
Forwarded.__f = true;
|
|
23
|
+
Forwarded.displayName = "ForwardRef(" + (fn.displayName || fn.name) + ")";
|
|
24
|
+
return Forwarded;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { forwardRef as t };
|