kiru 0.50.0-preview.0 → 0.50.0-preview.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.
Files changed (118) hide show
  1. package/dist/components/lazy.d.ts.map +1 -1
  2. package/dist/components/lazy.js +11 -136
  3. package/dist/components/lazy.js.map +1 -1
  4. package/dist/components/suspense.d.ts +7 -6
  5. package/dist/components/suspense.d.ts.map +1 -1
  6. package/dist/components/suspense.js +1 -4
  7. package/dist/components/suspense.js.map +1 -1
  8. package/dist/constants.d.ts +1 -2
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/constants.js +1 -2
  11. package/dist/constants.js.map +1 -1
  12. package/dist/hmr.d.ts +1 -0
  13. package/dist/hmr.d.ts.map +1 -1
  14. package/dist/hmr.js +7 -0
  15. package/dist/hmr.js.map +1 -1
  16. package/dist/recursiveRender.d.ts +6 -0
  17. package/dist/recursiveRender.d.ts.map +1 -0
  18. package/dist/recursiveRender.js +109 -0
  19. package/dist/recursiveRender.js.map +1 -0
  20. package/dist/renderToString.d.ts.map +1 -1
  21. package/dist/renderToString.js +6 -112
  22. package/dist/renderToString.js.map +1 -1
  23. package/dist/router/config.d.ts +3 -0
  24. package/dist/router/config.d.ts.map +1 -0
  25. package/dist/router/config.js +13 -0
  26. package/dist/router/config.js.map +1 -0
  27. package/dist/router/context.d.ts +15 -0
  28. package/dist/router/context.d.ts.map +1 -0
  29. package/dist/router/context.js +11 -0
  30. package/dist/router/context.js.map +1 -0
  31. package/dist/router/errors.d.ts +4 -0
  32. package/dist/router/errors.d.ts.map +1 -0
  33. package/dist/router/errors.js +7 -0
  34. package/dist/router/errors.js.map +1 -0
  35. package/dist/router/fileRouter.d.ts +48 -0
  36. package/dist/router/fileRouter.d.ts.map +1 -0
  37. package/dist/router/fileRouter.js +311 -0
  38. package/dist/router/fileRouter.js.map +1 -0
  39. package/dist/router/globals.d.ts +5 -0
  40. package/dist/router/globals.d.ts.map +1 -0
  41. package/dist/router/globals.js +4 -0
  42. package/dist/router/globals.js.map +1 -0
  43. package/dist/router/index.d.ts +7 -0
  44. package/dist/router/index.d.ts.map +1 -0
  45. package/dist/router/index.js +7 -0
  46. package/dist/router/index.js.map +1 -0
  47. package/dist/router/link.d.ts +8 -0
  48. package/dist/router/link.d.ts.map +1 -0
  49. package/dist/router/link.js +15 -0
  50. package/dist/router/link.js.map +1 -0
  51. package/dist/router/types.d.ts +63 -0
  52. package/dist/router/types.d.ts.map +1 -0
  53. package/dist/router/types.internal.d.ts +12 -0
  54. package/dist/router/types.internal.d.ts.map +1 -0
  55. package/dist/router/types.internal.js +2 -0
  56. package/dist/router/types.internal.js.map +1 -0
  57. package/dist/router/types.js +2 -0
  58. package/dist/router/types.js.map +1 -0
  59. package/dist/ssr/server.d.ts.map +1 -1
  60. package/dist/ssr/server.js +5 -109
  61. package/dist/ssr/server.js.map +1 -1
  62. package/dist/types.d.ts +2 -2
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.utils.d.ts +1 -8
  65. package/dist/types.utils.d.ts.map +1 -1
  66. package/dist/utils/vdom.d.ts.map +1 -1
  67. package/dist/utils/vdom.js +2 -5
  68. package/dist/utils/vdom.js.map +1 -1
  69. package/package.json +4 -8
  70. package/src/components/lazy.ts +12 -169
  71. package/src/components/suspense.ts +8 -10
  72. package/src/constants.ts +0 -2
  73. package/src/hmr.ts +8 -0
  74. package/src/recursiveRender.ts +127 -0
  75. package/src/renderToString.ts +7 -137
  76. package/src/router/config.ts +15 -0
  77. package/src/router/context.ts +23 -0
  78. package/src/router/errors.ts +6 -0
  79. package/src/router/fileRouter.ts +475 -0
  80. package/src/router/globals.ts +5 -0
  81. package/src/router/index.ts +6 -0
  82. package/src/router/link.ts +32 -0
  83. package/src/router/types.internal.ts +14 -0
  84. package/src/router/types.ts +81 -0
  85. package/src/ssr/server.ts +6 -136
  86. package/src/types.ts +0 -2
  87. package/src/types.utils.ts +1 -14
  88. package/src/utils/vdom.ts +1 -5
  89. package/dist/components/router/index.d.ts +0 -3
  90. package/dist/components/router/index.d.ts.map +0 -1
  91. package/dist/components/router/index.js +0 -3
  92. package/dist/components/router/index.js.map +0 -1
  93. package/dist/components/router/route.d.ts +0 -46
  94. package/dist/components/router/route.d.ts.map +0 -1
  95. package/dist/components/router/route.js +0 -8
  96. package/dist/components/router/route.js.map +0 -1
  97. package/dist/components/router/router.d.ts +0 -62
  98. package/dist/components/router/router.d.ts.map +0 -1
  99. package/dist/components/router/router.js +0 -177
  100. package/dist/components/router/router.js.map +0 -1
  101. package/dist/components/router/routerUtils.d.ts +0 -5
  102. package/dist/components/router/routerUtils.d.ts.map +0 -1
  103. package/dist/components/router/routerUtils.js +0 -39
  104. package/dist/components/router/routerUtils.js.map +0 -1
  105. package/dist/ssr/hydrationBoundary.d.ts +0 -27
  106. package/dist/ssr/hydrationBoundary.d.ts.map +0 -1
  107. package/dist/ssr/hydrationBoundary.js +0 -30
  108. package/dist/ssr/hydrationBoundary.js.map +0 -1
  109. package/dist/ssr/index.d.ts +0 -2
  110. package/dist/ssr/index.d.ts.map +0 -1
  111. package/dist/ssr/index.js +0 -2
  112. package/dist/ssr/index.js.map +0 -1
  113. package/src/components/router/index.ts +0 -2
  114. package/src/components/router/route.ts +0 -51
  115. package/src/components/router/router.ts +0 -273
  116. package/src/components/router/routerUtils.ts +0 -49
  117. package/src/ssr/hydrationBoundary.ts +0 -63
  118. package/src/ssr/index.ts +0 -1
@@ -0,0 +1,475 @@
1
+ import { Signal, computed, flushSync } from "../index.js"
2
+ import { __DEV__ } from "../env.js"
3
+ import { createElement } from "../element.js"
4
+ import { useState, useEffect } from "../hooks/index.js"
5
+ import { RouterContext, type FileRouterContextType } from "./context.js"
6
+ import { FileRouterDataLoadError } from "./errors.js"
7
+ import { fileRouterInstance } from "./globals.js"
8
+ import type {
9
+ ErrorPageProps,
10
+ FileRouterConfig,
11
+ PageConfig,
12
+ PageProps,
13
+ RouteQuery,
14
+ RouterState,
15
+ } from "./types.js"
16
+ import type {
17
+ DefaultComponentModule,
18
+ PageModule,
19
+ ViteImportMap,
20
+ } from "./types.internal.js"
21
+
22
+ export class FileRouterController {
23
+ private enableTransitions: boolean
24
+ private pages: ViteImportMap
25
+ private layouts: ViteImportMap
26
+ private abortController: AbortController
27
+ private currentPage: Signal<{
28
+ component: Kiru.FC<any>
29
+ config?: PageConfig
30
+ route: string
31
+ } | null>
32
+ private currentPageProps: Signal<PageProps<PageConfig>>
33
+ private currentLayouts: Signal<Kiru.FC[]>
34
+ private loading: Signal<boolean>
35
+ private state: Signal<RouterState>
36
+ private contextValue: Signal<FileRouterContextType>
37
+ private cleanups: (() => void)[] = []
38
+ private filePathToPageRoute: Map<
39
+ string,
40
+ {
41
+ route: string
42
+ config: PageConfig
43
+ }
44
+ >
45
+ private pageRouteToConfig: Map<string, PageConfig>
46
+ private currentRoute: string | null
47
+
48
+ constructor(props: FileRouterProps) {
49
+ fileRouterInstance.current = this
50
+ this.pages = {}
51
+ this.layouts = {}
52
+ this.abortController = new AbortController()
53
+ this.currentPage = new Signal(null)
54
+ this.currentPageProps = new Signal({})
55
+ this.currentLayouts = new Signal([])
56
+ this.loading = new Signal(true)
57
+ this.state = new Signal<RouterState>({
58
+ path: window.location.pathname,
59
+ params: {},
60
+ query: {},
61
+ signal: this.abortController.signal,
62
+ })
63
+ this.contextValue = computed<FileRouterContextType>(() => ({
64
+ state: this.state.value,
65
+ navigate: this.navigate.bind(this),
66
+ setQuery: this.setQuery.bind(this),
67
+ reload: (options?: { transition?: boolean }) =>
68
+ this.loadRoute(void 0, void 0, options?.transition),
69
+ }))
70
+ this.filePathToPageRoute = new Map()
71
+ this.pageRouteToConfig = new Map()
72
+ this.currentRoute = null
73
+
74
+ const {
75
+ pages,
76
+ layouts,
77
+ dir = "/pages",
78
+ baseUrl = "/",
79
+ transition,
80
+ } = props.config
81
+ this.enableTransitions = !!transition
82
+ const [normalizedDir, normalizedBaseUrl] = [
83
+ normalizePrefixPath(dir),
84
+ normalizePrefixPath(baseUrl),
85
+ ]
86
+ debugger
87
+ this.pages = formatViteImportMap(
88
+ pages as ViteImportMap,
89
+ normalizedDir,
90
+ normalizedBaseUrl
91
+ )
92
+ this.layouts = formatViteImportMap(
93
+ layouts as ViteImportMap,
94
+ normalizedDir,
95
+ normalizedBaseUrl
96
+ )
97
+
98
+ this.loadRoute()
99
+
100
+ const handlePopState = () => this.loadRoute()
101
+ window.addEventListener("popstate", handlePopState)
102
+ this.cleanups.push(() =>
103
+ window.removeEventListener("popstate", handlePopState)
104
+ )
105
+ }
106
+
107
+ public onPageConfigDefined<T extends PageConfig>(fp: string, config: T) {
108
+ const existing = this.filePathToPageRoute.get(fp)
109
+ if (existing === undefined) {
110
+ const route = this.currentRoute
111
+ if (!route) return
112
+ this.filePathToPageRoute.set(fp, { route, config })
113
+ return
114
+ }
115
+ const curPage = this.currentPage.value
116
+ if (curPage?.route === existing.route && config.loader) {
117
+ const p = this.currentPageProps.value
118
+ let transition = this.enableTransitions
119
+ if (config.loader.transition !== undefined) {
120
+ transition = config.loader.transition
121
+ }
122
+ const props = {
123
+ ...p,
124
+ loading: true,
125
+ data: null,
126
+ error: null,
127
+ }
128
+ handleStateTransition(this.state.value.signal, transition, () => {
129
+ this.currentPageProps.value = props
130
+ })
131
+
132
+ this.loadRouteData(config.loader, props, this.state.value, transition)
133
+ }
134
+
135
+ this.pageRouteToConfig.set(existing.route, config)
136
+ }
137
+
138
+ public getContextValue() {
139
+ return this.contextValue.value
140
+ }
141
+
142
+ public getChildren() {
143
+ const page = this.currentPage.value,
144
+ props = this.currentPageProps.value,
145
+ layouts = this.currentLayouts.value
146
+
147
+ if (page) {
148
+ // Wrap component with layouts (outermost first)
149
+ return layouts.reduceRight(
150
+ (children, Layout) => createElement(Layout, { children }),
151
+ createElement(page.component, props)
152
+ )
153
+ }
154
+
155
+ return null
156
+ }
157
+
158
+ public dispose() {
159
+ this.cleanups.forEach((cleanup) => cleanup())
160
+ }
161
+
162
+ private matchRoute(pathSegments: string[]) {
163
+ outer: for (const [route, pageModuleLoader] of Object.entries(this.pages)) {
164
+ const routeSegments = route.split("/").filter(Boolean)
165
+
166
+ const pathMatchingSegments = routeSegments.filter(
167
+ (seg) => !seg.startsWith("(") && !seg.endsWith(")")
168
+ )
169
+
170
+ if (pathMatchingSegments.length !== pathSegments.length) {
171
+ continue
172
+ }
173
+ const params: Record<string, string> = {}
174
+
175
+ for (let i = 0; i < pathMatchingSegments.length; i++) {
176
+ const routeSeg = pathMatchingSegments[i]
177
+ if (routeSeg.startsWith(":")) {
178
+ const key = routeSeg.slice(1)
179
+ params[key] = pathSegments[i]
180
+ continue
181
+ }
182
+ if (routeSeg !== pathSegments[i]) {
183
+ continue outer
184
+ }
185
+ }
186
+
187
+ return { route, pageModuleLoader, params, routeSegments }
188
+ }
189
+
190
+ return null
191
+ }
192
+
193
+ private async loadRoute(
194
+ path: string = window.location.pathname,
195
+ props: PageProps<PageConfig> = {},
196
+ enableTransition = this.enableTransitions
197
+ ): Promise<void> {
198
+ this.loading.value = true
199
+ this.abortController?.abort()
200
+
201
+ const query = parseQuery(window.location.search)
202
+ const controller = (this.abortController = new AbortController())
203
+ const signal = controller.signal
204
+
205
+ try {
206
+ const pathSegments = path.split("/").filter(Boolean)
207
+ const routeMatch = this.matchRoute(pathSegments)
208
+
209
+ if (!routeMatch) {
210
+ const _404 = this.matchRoute(["404"])
211
+ if (!_404) {
212
+ if (__DEV__) {
213
+ console.error(
214
+ `No 404 route defined (path: ${path}). See https://kirujs.dev/404 for more information.`
215
+ )
216
+ }
217
+ return
218
+ }
219
+ const errorProps = {
220
+ source: { path },
221
+ } satisfies ErrorPageProps
222
+
223
+ return this.navigate("/404", { replace: true, props: errorProps })
224
+ }
225
+
226
+ const { route, pageModuleLoader, params, routeSegments } = routeMatch
227
+
228
+ this.currentRoute = route
229
+ const pagePromise = pageModuleLoader()
230
+
231
+ const layoutPromises = ["/", ...routeSegments].reduce((acc, _, i) => {
232
+ const layoutPath = "/" + routeSegments.slice(0, i).join("/")
233
+ const layoutLoad = this.layouts[layoutPath]
234
+
235
+ if (!layoutLoad) {
236
+ return acc
237
+ }
238
+
239
+ return [...acc, layoutLoad()]
240
+ }, [] as Promise<DefaultComponentModule>[])
241
+
242
+ const [page, ...layouts] = await Promise.all([
243
+ pagePromise,
244
+ ...layoutPromises,
245
+ ])
246
+
247
+ this.currentRoute = null
248
+ if (controller.signal.aborted) return
249
+
250
+ if (typeof page.default !== "function") {
251
+ throw new Error("Route component must be a default exported function")
252
+ }
253
+
254
+ const routerState: RouterState = {
255
+ path,
256
+ params,
257
+ query,
258
+ signal,
259
+ }
260
+
261
+ let config = (page as unknown as PageModule).config
262
+ if (this.pageRouteToConfig.has(route)) {
263
+ config = this.pageRouteToConfig.get(route)
264
+ }
265
+
266
+ if (config?.loader) {
267
+ props = { ...props, loading: true, data: null, error: null }
268
+ this.loadRouteData(config.loader, props, routerState, enableTransition)
269
+ }
270
+
271
+ handleStateTransition(signal, enableTransition, () => {
272
+ this.currentPage.value = {
273
+ component: page.default,
274
+ config,
275
+ route: "/" + routeSegments.join("/"),
276
+ }
277
+ this.state.value = routerState
278
+ this.currentPageProps.value = props
279
+ this.currentLayouts.value = layouts
280
+ .filter((m) => typeof m.default === "function")
281
+ .map((m) => m.default)
282
+ })
283
+ } catch (error) {
284
+ console.error("Failed to load route component:", error)
285
+ this.currentPage.value = null
286
+ } finally {
287
+ this.loading.value = false
288
+ }
289
+ }
290
+
291
+ private async loadRouteData(
292
+ loader: NonNullable<PageConfig["loader"]>,
293
+ props: PageProps<PageConfig>,
294
+ routerState: RouterState,
295
+ enableTransition = this.enableTransitions
296
+ ) {
297
+ loader
298
+ .load(routerState)
299
+ .then(
300
+ (data) => ({ data, error: null }),
301
+ (error) => ({
302
+ data: null,
303
+ error: new FileRouterDataLoadError(error),
304
+ })
305
+ )
306
+ .then(({ data, error }) => {
307
+ if (routerState.signal.aborted) return
308
+
309
+ let transition = enableTransition
310
+ if (loader.transition !== undefined) {
311
+ transition = loader.transition
312
+ }
313
+
314
+ handleStateTransition(routerState.signal, transition, () => {
315
+ this.currentPageProps.value = {
316
+ ...props,
317
+ loading: false,
318
+ data,
319
+ error,
320
+ }
321
+ })
322
+ })
323
+ }
324
+
325
+ private async navigate(
326
+ path: string,
327
+ options?: {
328
+ replace?: boolean
329
+ transition?: boolean
330
+ props?: Record<string, unknown>
331
+ }
332
+ ) {
333
+ const f = options?.replace ? "replaceState" : "pushState"
334
+ window.history[f]({}, "", path)
335
+ window.dispatchEvent(new PopStateEvent("popstate", { state: {} }))
336
+ return this.loadRoute(path, options?.props, options?.transition)
337
+ }
338
+
339
+ private setQuery(query: RouteQuery) {
340
+ const queryString = buildQueryString(query)
341
+ const newUrl = `${this.state.value.path}${
342
+ queryString ? `?${queryString}` : ""
343
+ }`
344
+ window.history.pushState(null, "", newUrl)
345
+ this.state.value = { ...this.state.value, query }
346
+ }
347
+ }
348
+
349
+ export interface FileRouterProps {
350
+ /**
351
+ * The router configuration
352
+ * @example
353
+ * ```ts
354
+ *<FileRouter
355
+ config={{
356
+ dir: "/fbr-app", // optional, defaults to "/pages"
357
+ baseUrl: "/app", // optional, defaults to "/"
358
+ pages: import.meta.glob("/∗∗/index.tsx"),
359
+ layouts: import.meta.glob("/∗∗/layout.tsx"),
360
+ transition: true
361
+ }}
362
+ />
363
+ * ```
364
+ */
365
+ config: FileRouterConfig
366
+ }
367
+
368
+ export function FileRouter(props: FileRouterProps): JSX.Element {
369
+ const [controller] = useState(() => new FileRouterController(props))
370
+ useEffect(() => () => controller.dispose(), [controller])
371
+
372
+ return createElement(
373
+ RouterContext.Provider,
374
+ { value: controller.getContextValue() },
375
+ controller.getChildren()
376
+ )
377
+ }
378
+
379
+ // Utility functions
380
+
381
+ function parseQuery(
382
+ search: string
383
+ ): Record<string, string | string[] | undefined> {
384
+ const params = new URLSearchParams(search)
385
+ const query: Record<string, string | string[] | undefined> = {}
386
+
387
+ for (const [key, value] of params.entries()) {
388
+ if (query[key]) {
389
+ // Convert to array if multiple values
390
+ if (Array.isArray(query[key])) {
391
+ ;(query[key] as string[]).push(value)
392
+ } else {
393
+ query[key] = [query[key] as string, value]
394
+ }
395
+ } else {
396
+ query[key] = value
397
+ }
398
+ }
399
+
400
+ return query
401
+ }
402
+
403
+ function buildQueryString(
404
+ query: Record<string, string | string[] | undefined>
405
+ ): string {
406
+ const params = new URLSearchParams()
407
+
408
+ for (const [key, value] of Object.entries(query)) {
409
+ if (value !== undefined) {
410
+ if (Array.isArray(value)) {
411
+ value.forEach((v) => params.append(key, v))
412
+ } else {
413
+ params.set(key, value)
414
+ }
415
+ }
416
+ }
417
+
418
+ return params.toString()
419
+ }
420
+
421
+ function formatViteImportMap(
422
+ map: ViteImportMap,
423
+ dir: string,
424
+ baseUrl: string
425
+ ): ViteImportMap {
426
+ return Object.keys(map).reduce((acc, key) => {
427
+ let k = key
428
+ const dirIndex = k.indexOf(dir)
429
+ if (dirIndex === -1) {
430
+ return acc
431
+ }
432
+
433
+ k = k.slice(dirIndex + dir.length)
434
+ while (k.startsWith("/")) {
435
+ k = k.slice(1)
436
+ }
437
+ k = k.split("/").slice(0, -1).join("/") // remove filename
438
+ k = k.replace(/\[([^\]]+)\]/g, ":$1") // replace [param] with :param
439
+
440
+ return {
441
+ ...acc,
442
+ [baseUrl + k]: map[key],
443
+ }
444
+ }, {})
445
+ }
446
+
447
+ function normalizePrefixPath(path: string) {
448
+ while (path.startsWith(".")) {
449
+ path = path.slice(1)
450
+ }
451
+ path = `/${path}/`
452
+ while (path.startsWith("//")) {
453
+ path = path.slice(1)
454
+ }
455
+ while (path.endsWith("//")) {
456
+ path = path.slice(0, -1)
457
+ }
458
+ return path
459
+ }
460
+
461
+ function handleStateTransition(
462
+ signal: AbortSignal,
463
+ enableTransition: boolean,
464
+ callback: () => void
465
+ ) {
466
+ if (!enableTransition || typeof document.startViewTransition !== "function") {
467
+ return callback()
468
+ }
469
+ const vt = document.startViewTransition(() => {
470
+ callback()
471
+ flushSync()
472
+ })
473
+
474
+ signal.addEventListener("abort", () => vt.skipTransition())
475
+ }
@@ -0,0 +1,5 @@
1
+ import type { FileRouterController } from "./fileRouter"
2
+
3
+ export const fileRouterInstance = {
4
+ current: null as FileRouterController | null,
5
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./config.js"
2
+ export * from "./errors.js"
3
+ export { FileRouter, type FileRouterProps } from "./fileRouter.js"
4
+ export { useFileRouter } from "./context.js"
5
+ export * from "./link.js"
6
+ export * from "./types.js"
@@ -0,0 +1,32 @@
1
+ import type { ElementProps } from "../types"
2
+ import { createElement } from "../element.js"
3
+ import { useCallback } from "../hooks/index.js"
4
+ import { useFileRouter } from "./context.js"
5
+
6
+ export interface LinkProps extends ElementProps<"a"> {
7
+ to: string
8
+ replace?: boolean
9
+ transition?: boolean
10
+ }
11
+
12
+ export const Link: Kiru.FC<LinkProps> = ({
13
+ to,
14
+ onclick,
15
+ replace,
16
+ transition,
17
+ ...props
18
+ }) => {
19
+ const { navigate } = useFileRouter()
20
+
21
+ const handleClick = useCallback(
22
+ (e: Kiru.MouseEvent<HTMLAnchorElement>) => {
23
+ onclick?.(e)
24
+ if (e.defaultPrevented) return
25
+ e.preventDefault()
26
+ navigate(to, { replace, transition })
27
+ },
28
+ [to, navigate, onclick, replace]
29
+ )
30
+
31
+ return createElement("a", { href: to, onclick: handleClick, ...props })
32
+ }
@@ -0,0 +1,14 @@
1
+ import type { PageConfig } from "./types"
2
+
3
+ export interface DefaultComponentModule {
4
+ default: Kiru.FC
5
+ }
6
+
7
+ export interface PageModule {
8
+ default: DefaultComponentModule
9
+ config?: PageConfig
10
+ }
11
+
12
+ export interface ViteImportMap {
13
+ [fp: string]: () => Promise<DefaultComponentModule>
14
+ }
@@ -0,0 +1,81 @@
1
+ import { AsyncTaskState } from "../types.utils"
2
+ import { FileRouterDataLoadError } from "./errors"
3
+
4
+ export interface FileRouterConfig {
5
+ /**
6
+ * The directory to load routes from
7
+ * @default "/pages"
8
+ */
9
+ dir?: string
10
+ /**
11
+ * The import map to use for loading pages
12
+ * @example
13
+ * ```tsx
14
+ * <FileRouter config={{ pages: import.meta.glob("/∗∗/index.tsx"), ... }} />
15
+ * ```
16
+ */
17
+ pages: Record<string, unknown>
18
+ /**
19
+ * The import map to use for loading layouts
20
+ * @example
21
+ * ```tsx
22
+ * <FileRouter config={{ pages: import.meta.glob("/∗∗/layout.tsx"), ... }} />
23
+ * ```
24
+ */
25
+ layouts: Record<string, unknown>
26
+
27
+ /**
28
+ * The base url to use as a prefix for route matching
29
+ * @default "/"
30
+ */
31
+ baseUrl?: string
32
+
33
+ /**
34
+ * Enable transitions for all routes and loading states
35
+ * @default false
36
+ */
37
+ transition?: boolean
38
+ }
39
+
40
+ export interface RouteParams {
41
+ [key: string]: string
42
+ }
43
+
44
+ export interface RouteQuery {
45
+ [key: string]: string | string[] | undefined
46
+ }
47
+
48
+ export interface RouterState {
49
+ path: string
50
+ params: RouteParams
51
+ query: RouteQuery
52
+ signal: AbortSignal
53
+ }
54
+
55
+ type PageDataLoaderContext = RouterState & {}
56
+
57
+ export interface PageDataLoaderConfig<T = unknown> {
58
+ load: (context: PageDataLoaderContext) => Promise<T>
59
+ transition?: boolean
60
+ }
61
+
62
+ export interface PageConfig {
63
+ loader?: PageDataLoaderConfig
64
+ // title?: string
65
+ // description?: string
66
+ // meta?: Record<string, string>
67
+ }
68
+
69
+ export type PageProps<T extends PageConfig> =
70
+ T["loader"] extends PageDataLoaderConfig
71
+ ? AsyncTaskState<
72
+ Awaited<ReturnType<T["loader"]["load"]>>,
73
+ FileRouterDataLoadError
74
+ >
75
+ : {}
76
+
77
+ export interface ErrorPageProps {
78
+ source?: {
79
+ path: string
80
+ }
81
+ }