@alepha/react 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.cjs +0 -0
- package/dist/index.browser.js +0 -0
- package/dist/index.cjs +11 -14
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -14
- package/package.json +4 -4
- package/src/components/Link.tsx +0 -22
- package/src/components/NestedView.tsx +0 -36
- package/src/constants/SSID.ts +0 -1
- package/src/contexts/RouterContext.ts +0 -15
- package/src/contexts/RouterLayerContext.ts +0 -10
- package/src/descriptors/$auth.ts +0 -28
- package/src/descriptors/$page.ts +0 -144
- package/src/errors/RedirectionError.ts +0 -7
- package/src/hooks/RouterHookApi.ts +0 -154
- package/src/hooks/useActive.ts +0 -57
- package/src/hooks/useAuth.ts +0 -29
- package/src/hooks/useClient.ts +0 -6
- package/src/hooks/useInject.ts +0 -12
- package/src/hooks/useQueryParams.ts +0 -59
- package/src/hooks/useRouter.ts +0 -28
- package/src/hooks/useRouterEvents.ts +0 -43
- package/src/hooks/useRouterState.ts +0 -23
- package/src/index.browser.ts +0 -21
- package/src/index.shared.ts +0 -28
- package/src/index.ts +0 -46
- package/src/providers/PageDescriptorProvider.ts +0 -52
- package/src/providers/ReactAuthProvider.ts +0 -410
- package/src/providers/ReactBrowserProvider.ts +0 -250
- package/src/providers/ReactServerProvider.ts +0 -301
- package/src/services/Auth.ts +0 -45
- package/src/services/Router.ts +0 -855
package/src/services/Router.ts
DELETED
|
@@ -1,855 +0,0 @@
|
|
|
1
|
-
import { $inject, $logger, Alepha, EventEmitter } from "@alepha/core";
|
|
2
|
-
import type { MatchFunction, ParamData } from "path-to-regexp";
|
|
3
|
-
import { compile, match } from "path-to-regexp";
|
|
4
|
-
import type { ReactNode } from "react";
|
|
5
|
-
import { createElement } from "react";
|
|
6
|
-
import NestedView from "../components/NestedView";
|
|
7
|
-
import { RouterContext } from "../contexts/RouterContext";
|
|
8
|
-
import { RouterLayerContext } from "../contexts/RouterLayerContext";
|
|
9
|
-
import type { PageContext, PageDescriptorOptions } from "../descriptors/$page";
|
|
10
|
-
import { RedirectionError } from "../errors/RedirectionError";
|
|
11
|
-
|
|
12
|
-
export class Router extends EventEmitter<RouterEvents> {
|
|
13
|
-
protected readonly log = $logger();
|
|
14
|
-
protected readonly alepha = $inject(Alepha);
|
|
15
|
-
protected readonly pages: PageRoute[] = [];
|
|
16
|
-
protected notFoundPageRoute?: PageRoute;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Get the page by name.
|
|
20
|
-
*
|
|
21
|
-
* @param name - Page name
|
|
22
|
-
* @return PageRoute
|
|
23
|
-
*/
|
|
24
|
-
public page(name: string): PageRoute {
|
|
25
|
-
const found = this.pages.find((it) => it.name === name);
|
|
26
|
-
if (!found) {
|
|
27
|
-
throw new Error(`Page ${name} not found`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return found;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
*/
|
|
36
|
-
public root(state: RouterState, context: PageContext = {}): ReactNode {
|
|
37
|
-
return createElement(
|
|
38
|
-
RouterContext.Provider,
|
|
39
|
-
{
|
|
40
|
-
value: {
|
|
41
|
-
state,
|
|
42
|
-
router: this,
|
|
43
|
-
alepha: this.alepha,
|
|
44
|
-
args: {
|
|
45
|
-
user: context.user,
|
|
46
|
-
cookies: context.cookies,
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
state.layers[0]?.element,
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
56
|
-
* @param url
|
|
57
|
-
* @param options
|
|
58
|
-
*/
|
|
59
|
-
public async render(
|
|
60
|
-
url: string,
|
|
61
|
-
options: RouterRenderOptions = {},
|
|
62
|
-
): Promise<RouterRenderResult> {
|
|
63
|
-
const [pathname, search = ""] = url.split("?");
|
|
64
|
-
const state: RouterState = {
|
|
65
|
-
pathname,
|
|
66
|
-
search,
|
|
67
|
-
layers: [],
|
|
68
|
-
context: {},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
await this.emit("begin", undefined);
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
let layers = await this.match(url, options, state.context);
|
|
75
|
-
if (layers.length === 0) {
|
|
76
|
-
if (this.notFoundPageRoute) {
|
|
77
|
-
layers = await this.createLayers(url, this.notFoundPageRoute);
|
|
78
|
-
} else {
|
|
79
|
-
layers.push({
|
|
80
|
-
name: "not-found",
|
|
81
|
-
element: "Not Found",
|
|
82
|
-
index: 0,
|
|
83
|
-
path: "/",
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
state.layers = layers;
|
|
89
|
-
await this.emit("success", undefined);
|
|
90
|
-
} catch (e) {
|
|
91
|
-
if (e instanceof RedirectionError) {
|
|
92
|
-
// redirect - stop processing
|
|
93
|
-
return {
|
|
94
|
-
element: null,
|
|
95
|
-
layers: [],
|
|
96
|
-
redirect: typeof e.page === "string" ? e.page : this.href(e.page),
|
|
97
|
-
context: state.context,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.log.error(e);
|
|
102
|
-
|
|
103
|
-
state.layers = [
|
|
104
|
-
{
|
|
105
|
-
name: "error",
|
|
106
|
-
element: this.renderError(e as Error),
|
|
107
|
-
index: 0,
|
|
108
|
-
path: "/",
|
|
109
|
-
},
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
await this.emit("error", e as Error);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (options.state) {
|
|
116
|
-
// stateful (csr)
|
|
117
|
-
options.state.layers = state.layers;
|
|
118
|
-
options.state.pathname = state.pathname;
|
|
119
|
-
options.state.search = state.search;
|
|
120
|
-
options.state.context = state.context;
|
|
121
|
-
|
|
122
|
-
await this.emit("end", options.state);
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
element: this.root(options.state, options.args),
|
|
126
|
-
layers: options.state.layers,
|
|
127
|
-
context: state.context,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// stateless (ssr)
|
|
132
|
-
await this.emit("end", state);
|
|
133
|
-
return {
|
|
134
|
-
element: this.root(state, options.args),
|
|
135
|
-
layers: state.layers,
|
|
136
|
-
context: state.context,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
* @param url
|
|
143
|
-
* @param options
|
|
144
|
-
* @param context
|
|
145
|
-
* @protected
|
|
146
|
-
*/
|
|
147
|
-
public async match(
|
|
148
|
-
url: string,
|
|
149
|
-
options: RouterMatchOptions = {},
|
|
150
|
-
context: RouterRenderContext = {},
|
|
151
|
-
): Promise<Layer[]> {
|
|
152
|
-
const pages = this.pages;
|
|
153
|
-
const previous = options.previous;
|
|
154
|
-
|
|
155
|
-
const [pathname, search] = url.split("?");
|
|
156
|
-
|
|
157
|
-
for (const route of pages) {
|
|
158
|
-
if (route.children?.find((it) => !it.path || it.path === "/")) continue;
|
|
159
|
-
if (!route.match) continue;
|
|
160
|
-
|
|
161
|
-
const match = route.match.exec(pathname);
|
|
162
|
-
if (match) {
|
|
163
|
-
const params = match.params ?? {};
|
|
164
|
-
const query: Record<string, string> = {};
|
|
165
|
-
if (search) {
|
|
166
|
-
for (const [key, value] of new URLSearchParams(search).entries()) {
|
|
167
|
-
query[key] = String(value);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return await this.createLayers(
|
|
172
|
-
url,
|
|
173
|
-
route,
|
|
174
|
-
params,
|
|
175
|
-
query,
|
|
176
|
-
previous,
|
|
177
|
-
options.args,
|
|
178
|
-
context,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return [];
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Create layers for the given route.
|
|
188
|
-
*
|
|
189
|
-
* @param url
|
|
190
|
-
* @param route
|
|
191
|
-
* @param params
|
|
192
|
-
* @param query
|
|
193
|
-
* @param previous
|
|
194
|
-
* @param args
|
|
195
|
-
* @param renderContext
|
|
196
|
-
* @protected
|
|
197
|
-
*/
|
|
198
|
-
public async createLayers(
|
|
199
|
-
url: string,
|
|
200
|
-
route: PageRoute,
|
|
201
|
-
params: Record<string, any> = {},
|
|
202
|
-
query: Record<string, string> = {},
|
|
203
|
-
previous: PreviousLayerData[] = [],
|
|
204
|
-
args?: PageContext,
|
|
205
|
-
renderContext?: RouterRenderContext,
|
|
206
|
-
): Promise<Layer[]> {
|
|
207
|
-
const layers: Layer[] = [];
|
|
208
|
-
let context: Record<string, any> = {};
|
|
209
|
-
const stack: Array<RouterStackItem> = [{ route }];
|
|
210
|
-
|
|
211
|
-
let parent = route.parent;
|
|
212
|
-
while (parent) {
|
|
213
|
-
stack.unshift({ route: parent });
|
|
214
|
-
parent = parent.parent;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let forceRefresh = false;
|
|
218
|
-
|
|
219
|
-
for (let i = 0; i < stack.length; i++) {
|
|
220
|
-
const it = stack[i];
|
|
221
|
-
const route = it.route;
|
|
222
|
-
const config: Record<string, any> = {};
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
config.query = route.schema?.query
|
|
226
|
-
? this.alepha.parse(route.schema.query, query)
|
|
227
|
-
: query;
|
|
228
|
-
} catch (e) {
|
|
229
|
-
it.error = e as Error;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
config.params = route.schema?.params
|
|
235
|
-
? this.alepha.parse(route.schema.params, params)
|
|
236
|
-
: params;
|
|
237
|
-
} catch (e) {
|
|
238
|
-
it.error = e as Error;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// save config
|
|
243
|
-
it.config = {
|
|
244
|
-
...config,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// no resolve, render a basic view by default
|
|
248
|
-
if (!route.resolve) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// check if previous layer is the same, reuse if possible
|
|
253
|
-
if (previous?.[i] && !forceRefresh && previous[i].name === route.name) {
|
|
254
|
-
const url = (str?: string) => (str ? str.replace(/\/\/+/g, "/") : "/");
|
|
255
|
-
|
|
256
|
-
const prev = JSON.stringify({
|
|
257
|
-
part: url(previous[i].part),
|
|
258
|
-
params: previous[i].config?.params ?? {},
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const curr = JSON.stringify({
|
|
262
|
-
part: url(route.path),
|
|
263
|
-
params: config.params ?? {},
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
if (prev === curr) {
|
|
267
|
-
// part is the same, reuse previous layer
|
|
268
|
-
it.props = previous[i].props;
|
|
269
|
-
context = {
|
|
270
|
-
...context,
|
|
271
|
-
...it.props,
|
|
272
|
-
};
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
// part is different, force refresh of next layers
|
|
276
|
-
forceRefresh = true;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
const props =
|
|
281
|
-
(await route.resolve?.(
|
|
282
|
-
{
|
|
283
|
-
...config,
|
|
284
|
-
...context,
|
|
285
|
-
context: args,
|
|
286
|
-
url,
|
|
287
|
-
} as any,
|
|
288
|
-
args ?? {},
|
|
289
|
-
)) ?? {};
|
|
290
|
-
|
|
291
|
-
// save props
|
|
292
|
-
it.props = {
|
|
293
|
-
...props,
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// add props to context
|
|
297
|
-
context = {
|
|
298
|
-
...context,
|
|
299
|
-
...props,
|
|
300
|
-
};
|
|
301
|
-
} catch (e) {
|
|
302
|
-
// check if we need to redirect
|
|
303
|
-
if (e instanceof RedirectionError) {
|
|
304
|
-
throw e; // redirect - stop processing
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
this.log.error(e);
|
|
308
|
-
|
|
309
|
-
it.error = e as Error;
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
let acc = "";
|
|
315
|
-
for (let i = 0; i < stack.length; i++) {
|
|
316
|
-
const it = stack[i];
|
|
317
|
-
const props = it.props ?? {};
|
|
318
|
-
|
|
319
|
-
const params = { ...it.config?.params };
|
|
320
|
-
for (const key of Object.keys(params)) {
|
|
321
|
-
params[key] = String(params[key]);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (it.route.helmet && renderContext) {
|
|
325
|
-
this.mergeRenderContext(it.route, renderContext, {
|
|
326
|
-
...props,
|
|
327
|
-
...context,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
acc += "/";
|
|
332
|
-
acc += it.route.path ? compile(it.route.path)(params) : "";
|
|
333
|
-
const path = acc.replace(/\/+/, "/");
|
|
334
|
-
|
|
335
|
-
// handler has thrown an error, render an error view
|
|
336
|
-
if (it.error) {
|
|
337
|
-
const errorHandler = this.getErrorHandler(it.route);
|
|
338
|
-
const element = errorHandler
|
|
339
|
-
? errorHandler({
|
|
340
|
-
...it.config,
|
|
341
|
-
error: it.error,
|
|
342
|
-
url,
|
|
343
|
-
})
|
|
344
|
-
: this.renderError(it.error);
|
|
345
|
-
|
|
346
|
-
layers.push({
|
|
347
|
-
props,
|
|
348
|
-
name: it.route.name,
|
|
349
|
-
part: it.route.path,
|
|
350
|
-
config: it.config,
|
|
351
|
-
element: this.renderView(i + 1, path, element),
|
|
352
|
-
index: i + 1,
|
|
353
|
-
path,
|
|
354
|
-
});
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// normal use case
|
|
359
|
-
|
|
360
|
-
const layer = await this.createElement(it.route, {
|
|
361
|
-
...props,
|
|
362
|
-
...context,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
layers.push({
|
|
366
|
-
name: it.route.name,
|
|
367
|
-
props,
|
|
368
|
-
part: it.route.path,
|
|
369
|
-
config: it.config,
|
|
370
|
-
element: this.renderView(i + 1, path, layer),
|
|
371
|
-
index: i + 1,
|
|
372
|
-
path,
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return layers;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
*
|
|
381
|
-
* @param route
|
|
382
|
-
* @protected
|
|
383
|
-
*/
|
|
384
|
-
protected getErrorHandler(route: PageRoute) {
|
|
385
|
-
if (route.errorHandler) return route.errorHandler;
|
|
386
|
-
let parent = route.parent;
|
|
387
|
-
while (parent) {
|
|
388
|
-
if (parent.errorHandler) return parent.errorHandler;
|
|
389
|
-
parent = parent.parent;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
*
|
|
395
|
-
* @param page
|
|
396
|
-
* @param props
|
|
397
|
-
* @protected
|
|
398
|
-
*/
|
|
399
|
-
protected async createElement(
|
|
400
|
-
page: PageRoute,
|
|
401
|
-
props: Record<string, any>,
|
|
402
|
-
): Promise<ReactNode> {
|
|
403
|
-
if (page.lazy) {
|
|
404
|
-
const component = await page.lazy(); // load component
|
|
405
|
-
return createElement(component.default, props);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (page.component) {
|
|
409
|
-
return createElement(page.component, props);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return undefined;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Merge the render context with the page context.
|
|
417
|
-
*
|
|
418
|
-
* @param page
|
|
419
|
-
* @param ctx
|
|
420
|
-
* @param props
|
|
421
|
-
* @protected
|
|
422
|
-
*/
|
|
423
|
-
protected mergeRenderContext(
|
|
424
|
-
page: PageRoute,
|
|
425
|
-
ctx: RouterRenderContext,
|
|
426
|
-
props: Record<string, any>,
|
|
427
|
-
): void {
|
|
428
|
-
if (page.helmet) {
|
|
429
|
-
const helmet =
|
|
430
|
-
typeof page.helmet === "function" ? page.helmet(props) : page.helmet;
|
|
431
|
-
if (helmet.title) {
|
|
432
|
-
ctx.helmet ??= {};
|
|
433
|
-
|
|
434
|
-
if (ctx.helmet?.title) {
|
|
435
|
-
ctx.helmet.title = `${helmet.title} - ${ctx.helmet.title}`;
|
|
436
|
-
} else {
|
|
437
|
-
ctx.helmet.title = helmet.title;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
*
|
|
445
|
-
* @param e
|
|
446
|
-
* @protected
|
|
447
|
-
*/
|
|
448
|
-
protected renderError(e: Error): ReactNode {
|
|
449
|
-
return createElement("pre", { style: { overflow: "auto" } }, `${e.stack}`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Render an empty view.
|
|
454
|
-
*
|
|
455
|
-
* @protected
|
|
456
|
-
*/
|
|
457
|
-
protected renderEmptyView(): ReactNode {
|
|
458
|
-
return createElement(NestedView, {});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Create a valid href for the given page.
|
|
463
|
-
* @param page
|
|
464
|
-
* @param params
|
|
465
|
-
*/
|
|
466
|
-
public href(
|
|
467
|
-
page: { options: { name?: string } },
|
|
468
|
-
params: Record<string, any> = {},
|
|
469
|
-
): string {
|
|
470
|
-
const found = this.pages.find((it) => it.name === page.options.name);
|
|
471
|
-
if (!found) {
|
|
472
|
-
throw new Error(`Page ${page.options.name} not found`);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
let url = found.path ?? "";
|
|
476
|
-
let parent = found.parent;
|
|
477
|
-
while (parent) {
|
|
478
|
-
url = `${parent.path ?? ""}/${url}`;
|
|
479
|
-
parent = parent.parent;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
url = compile(url)(params);
|
|
483
|
-
|
|
484
|
-
return url.replace(/\/\/+/g, "/") || "/";
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
*
|
|
489
|
-
* @param index
|
|
490
|
-
* @param path
|
|
491
|
-
* @param view
|
|
492
|
-
* @protected
|
|
493
|
-
*/
|
|
494
|
-
protected renderView(
|
|
495
|
-
index: number,
|
|
496
|
-
path: string,
|
|
497
|
-
view: ReactNode = this.renderEmptyView(),
|
|
498
|
-
): ReactNode {
|
|
499
|
-
return createElement(
|
|
500
|
-
RouterLayerContext.Provider,
|
|
501
|
-
{
|
|
502
|
-
value: {
|
|
503
|
-
index,
|
|
504
|
-
path,
|
|
505
|
-
},
|
|
506
|
-
},
|
|
507
|
-
view,
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
*
|
|
513
|
-
* @param entry
|
|
514
|
-
*/
|
|
515
|
-
public add(entry: PageRouteEntry) {
|
|
516
|
-
if (this.alepha.isReady()) {
|
|
517
|
-
throw new Error("Router is already initialized");
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (entry.notFoundHandler) {
|
|
521
|
-
this.notFoundPageRoute = {
|
|
522
|
-
name: "not-found",
|
|
523
|
-
component: entry.notFoundHandler,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
entry.name ??= this.nextId();
|
|
528
|
-
const page = entry as PageRoute;
|
|
529
|
-
|
|
530
|
-
page.match = this.createMatchFunction(page);
|
|
531
|
-
this.pages.push(page);
|
|
532
|
-
|
|
533
|
-
if (page.children) {
|
|
534
|
-
for (const child of page.children) {
|
|
535
|
-
child.parent = page;
|
|
536
|
-
this.add(child);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Create a match function for the given page.
|
|
543
|
-
*
|
|
544
|
-
* @param page
|
|
545
|
-
* @protected
|
|
546
|
-
*/
|
|
547
|
-
protected createMatchFunction(
|
|
548
|
-
page: PageRoute,
|
|
549
|
-
): { exec: MatchFunction<ParamData>; path: string } | undefined {
|
|
550
|
-
let url = page.path ?? "/";
|
|
551
|
-
let target = page.parent;
|
|
552
|
-
while (target) {
|
|
553
|
-
url = `${target.path ?? ""}/${url}`;
|
|
554
|
-
target = target.parent;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
let path = url.replace(/\/\/+/g, "/");
|
|
558
|
-
|
|
559
|
-
if (path.endsWith("/")) {
|
|
560
|
-
// remove trailing slash
|
|
561
|
-
path = path.slice(0, -1);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (path.includes("?")) {
|
|
565
|
-
return {
|
|
566
|
-
exec: match(path.split("?")[0]),
|
|
567
|
-
path,
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return {
|
|
572
|
-
exec: match(path),
|
|
573
|
-
path,
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
*
|
|
579
|
-
*/
|
|
580
|
-
public empty() {
|
|
581
|
-
return this.pages.length === 0;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
*
|
|
586
|
-
* @protected
|
|
587
|
-
*/
|
|
588
|
-
protected _next = 0;
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
*
|
|
592
|
-
* @protected
|
|
593
|
-
*/
|
|
594
|
-
protected nextId(): string {
|
|
595
|
-
this._next += 1;
|
|
596
|
-
return `P${this._next}`;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
601
|
-
|
|
602
|
-
export interface PageRouteEntry
|
|
603
|
-
extends Omit<PageDescriptorOptions, "children" | "parent"> {
|
|
604
|
-
/**
|
|
605
|
-
*
|
|
606
|
-
*/
|
|
607
|
-
name?: string;
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
*
|
|
611
|
-
*/
|
|
612
|
-
match?: {
|
|
613
|
-
/**
|
|
614
|
-
*
|
|
615
|
-
*/
|
|
616
|
-
exec: MatchFunction<ParamData>;
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
*
|
|
620
|
-
*/
|
|
621
|
-
path: string;
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
*
|
|
626
|
-
*/
|
|
627
|
-
children?: PageRouteEntry[];
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
*
|
|
631
|
-
*/
|
|
632
|
-
parent?: PageRoute;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
export interface PageRoute extends PageRouteEntry {
|
|
636
|
-
/**
|
|
637
|
-
*
|
|
638
|
-
*/
|
|
639
|
-
name: string;
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
*
|
|
643
|
-
*/
|
|
644
|
-
parent?: PageRoute;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
export interface Layer {
|
|
648
|
-
/**
|
|
649
|
-
*
|
|
650
|
-
*/
|
|
651
|
-
config?: {
|
|
652
|
-
/**
|
|
653
|
-
*
|
|
654
|
-
*/
|
|
655
|
-
query?: Record<string, any>;
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
*
|
|
659
|
-
*/
|
|
660
|
-
params?: Record<string, any>;
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
*
|
|
664
|
-
*/
|
|
665
|
-
context?: Record<string, any>;
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
*
|
|
670
|
-
*/
|
|
671
|
-
name: string;
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
*
|
|
675
|
-
*/
|
|
676
|
-
props?: Record<string, any>;
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
*
|
|
680
|
-
*/
|
|
681
|
-
part?: string;
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
*
|
|
685
|
-
*/
|
|
686
|
-
element: ReactNode;
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
*
|
|
690
|
-
*/
|
|
691
|
-
index: number;
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
*
|
|
695
|
-
*/
|
|
696
|
-
path: string;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
*
|
|
701
|
-
*/
|
|
702
|
-
export type PreviousLayerData = Omit<Layer, "element">;
|
|
703
|
-
|
|
704
|
-
export interface AnchorProps {
|
|
705
|
-
/**
|
|
706
|
-
*
|
|
707
|
-
*/
|
|
708
|
-
href?: string;
|
|
709
|
-
|
|
710
|
-
/**
|
|
711
|
-
*
|
|
712
|
-
* @param ev
|
|
713
|
-
*/
|
|
714
|
-
onClick?: (ev: any) => any;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
export interface RouterMatchOptions {
|
|
718
|
-
/**
|
|
719
|
-
*
|
|
720
|
-
*/
|
|
721
|
-
previous?: PreviousLayerData[];
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
*
|
|
725
|
-
*/
|
|
726
|
-
args?: PageContext;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export interface RouterEvents {
|
|
730
|
-
/**
|
|
731
|
-
*
|
|
732
|
-
*/
|
|
733
|
-
begin: undefined;
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
*
|
|
737
|
-
*/
|
|
738
|
-
success: undefined;
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
*
|
|
742
|
-
*/
|
|
743
|
-
error: Error;
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
*
|
|
747
|
-
*/
|
|
748
|
-
end: RouterState;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
export interface RouterState {
|
|
752
|
-
/**
|
|
753
|
-
*
|
|
754
|
-
*/
|
|
755
|
-
pathname: string;
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
*
|
|
759
|
-
*/
|
|
760
|
-
search: string;
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
*
|
|
764
|
-
*/
|
|
765
|
-
layers: Array<Layer>;
|
|
766
|
-
|
|
767
|
-
/**
|
|
768
|
-
*
|
|
769
|
-
*/
|
|
770
|
-
context: RouterRenderContext;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export interface RouterRenderContext {
|
|
774
|
-
/**
|
|
775
|
-
*
|
|
776
|
-
*/
|
|
777
|
-
helmet?: RouterRenderHelmetContext;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
export interface RouterRenderOptions extends RouterMatchOptions {
|
|
781
|
-
/**
|
|
782
|
-
* State to update.
|
|
783
|
-
*/
|
|
784
|
-
state?: RouterState;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
export interface RouterStackItem {
|
|
788
|
-
/**
|
|
789
|
-
*
|
|
790
|
-
*/
|
|
791
|
-
route: PageRoute;
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
*
|
|
795
|
-
*/
|
|
796
|
-
config?: Record<string, any>;
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
*
|
|
800
|
-
*/
|
|
801
|
-
props?: Record<string, any>;
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
*
|
|
805
|
-
*/
|
|
806
|
-
error?: Error;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
export interface RouterRenderHelmetContext {
|
|
810
|
-
/**
|
|
811
|
-
*
|
|
812
|
-
*/
|
|
813
|
-
title?: string;
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
*
|
|
817
|
-
*/
|
|
818
|
-
html?: {
|
|
819
|
-
attributes?: Record<string, string>;
|
|
820
|
-
};
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
*
|
|
824
|
-
*/
|
|
825
|
-
body?: {
|
|
826
|
-
attributes?: Record<string, string>;
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
*
|
|
831
|
-
*/
|
|
832
|
-
meta?: Array<{ name: string; content: string }>;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
export interface RouterRenderResult {
|
|
836
|
-
/**
|
|
837
|
-
*
|
|
838
|
-
*/
|
|
839
|
-
element: ReactNode;
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
*
|
|
843
|
-
*/
|
|
844
|
-
layers: Layer[];
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
*
|
|
848
|
-
*/
|
|
849
|
-
redirect?: string;
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
*
|
|
853
|
-
*/
|
|
854
|
-
context: RouterRenderContext;
|
|
855
|
-
}
|