@benjavicente/angular-router-experimental 1.142.11
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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/fesm2022/tanstack-angular-router-experimental-experimental.mjs +920 -0
- package/dist/fesm2022/tanstack-angular-router-experimental.mjs +4131 -0
- package/dist/types/tanstack-angular-router-experimental-experimental.d.ts +110 -0
- package/dist/types/tanstack-angular-router-experimental.d.ts +733 -0
- package/experimental/injectRouteErrorHandler.ts +51 -0
- package/experimental/public_api.ts +8 -0
- package/package.json +98 -0
- package/src/DefaultNotFound.ts +9 -0
- package/src/Link.ts +352 -0
- package/src/Match.ts +338 -0
- package/src/Matches.ts +37 -0
- package/src/RouterProvider.ts +162 -0
- package/src/document/build-match-managed-document.ts +308 -0
- package/src/document/document-dehydration.ts +27 -0
- package/src/document/document-equality.ts +29 -0
- package/src/document/document-router-token.ts +6 -0
- package/src/document/index.ts +33 -0
- package/src/document/install-unified-document-sync.ts +108 -0
- package/src/document/managed-document-types.ts +36 -0
- package/src/document/managed-dom.ts +307 -0
- package/src/document/provide-tanstack-body-managed-tags.ts +78 -0
- package/src/document/provide-tanstack-document-title.ts +59 -0
- package/src/document/provide-tanstack-document.ts +62 -0
- package/src/document/provide-tanstack-head-managed-tags.ts +63 -0
- package/src/fileRoute.ts +232 -0
- package/src/index.ts +173 -0
- package/src/injectBlocker.ts +196 -0
- package/src/injectCanGoBack.ts +11 -0
- package/src/injectErrorState.ts +21 -0
- package/src/injectIntersectionObserver.ts +28 -0
- package/src/injectLoaderData.ts +49 -0
- package/src/injectLoaderDeps.ts +45 -0
- package/src/injectLocation.ts +38 -0
- package/src/injectMatch.ts +122 -0
- package/src/injectMatchRoute.ts +58 -0
- package/src/injectMatches.ts +79 -0
- package/src/injectNavigate.ts +24 -0
- package/src/injectParams.ts +71 -0
- package/src/injectRouteContext.ts +31 -0
- package/src/injectRouter.ts +17 -0
- package/src/injectRouterState.ts +53 -0
- package/src/injectSearch.ts +71 -0
- package/src/injectStore.ts +87 -0
- package/src/matchInjectorToken.ts +23 -0
- package/src/renderer/injectIsCatchingError.ts +40 -0
- package/src/renderer/injectRender.ts +69 -0
- package/src/route.ts +641 -0
- package/src/router.ts +141 -0
- package/src/routerInjectionToken.ts +24 -0
- package/src/routerStores.ts +107 -0
- package/src/ssr-scroll-restoration.ts +48 -0
- package/src/transitioner.ts +255 -0
package/src/Match.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
DestroyRef,
|
|
4
|
+
EnvironmentInjector,
|
|
5
|
+
afterNextRender,
|
|
6
|
+
computed,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
isDevMode,
|
|
11
|
+
} from '@angular/core'
|
|
12
|
+
import {
|
|
13
|
+
AnyRoute,
|
|
14
|
+
AnyRouter,
|
|
15
|
+
getLocationChangeInfo,
|
|
16
|
+
rootRouteId,
|
|
17
|
+
} from '@benjavicente/router-core'
|
|
18
|
+
import { injectRouter } from './injectRouter'
|
|
19
|
+
import { injectStore } from './injectStore'
|
|
20
|
+
import { DefaultNotFoundComponent } from './DefaultNotFound'
|
|
21
|
+
import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
|
|
22
|
+
import { injectRender } from './renderer/injectRender'
|
|
23
|
+
import { ERROR_STATE_INJECTOR_TOKEN } from './injectErrorState'
|
|
24
|
+
import { injectIsCatchingError } from './renderer/injectIsCatchingError'
|
|
25
|
+
import type { Signal } from '@angular/core'
|
|
26
|
+
import type { NearestMatchContextValue } from './matchInjectorToken'
|
|
27
|
+
|
|
28
|
+
function injectOnRendered({
|
|
29
|
+
parentRouteIsRoot,
|
|
30
|
+
}: {
|
|
31
|
+
parentRouteIsRoot: Signal<boolean>
|
|
32
|
+
}) {
|
|
33
|
+
const router = injectRouter({ warn: false })
|
|
34
|
+
const envInjector = inject(EnvironmentInjector)
|
|
35
|
+
const destroyRef = inject(DestroyRef)
|
|
36
|
+
const location = injectStore(
|
|
37
|
+
router.stores.resolvedLocation,
|
|
38
|
+
(resolvedLocation) => resolvedLocation?.state.__TSR_key,
|
|
39
|
+
)
|
|
40
|
+
const loadedAt = injectStore(router.stores.loadedAt, (value) => value)
|
|
41
|
+
|
|
42
|
+
let prevHref: string | undefined
|
|
43
|
+
let renderGeneration = 0
|
|
44
|
+
|
|
45
|
+
destroyRef.onDestroy(() => {
|
|
46
|
+
renderGeneration++
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
effect(() => {
|
|
50
|
+
if (!parentRouteIsRoot()) return
|
|
51
|
+
location()
|
|
52
|
+
loadedAt()
|
|
53
|
+
|
|
54
|
+
const gen = ++renderGeneration
|
|
55
|
+
afterNextRender(
|
|
56
|
+
{
|
|
57
|
+
read: () => {
|
|
58
|
+
if (gen !== renderGeneration) return
|
|
59
|
+
if (!parentRouteIsRoot()) return
|
|
60
|
+
if (router.isServer) return
|
|
61
|
+
|
|
62
|
+
const currentHref = router.latestLocation.href
|
|
63
|
+
if (prevHref !== undefined && prevHref === currentHref) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
prevHref = currentHref
|
|
67
|
+
|
|
68
|
+
router.emit({
|
|
69
|
+
type: 'onRendered',
|
|
70
|
+
...getLocationChangeInfo(
|
|
71
|
+
router.stores.location.state,
|
|
72
|
+
router.stores.resolvedLocation.state,
|
|
73
|
+
),
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{ injector: envInjector },
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Component({
|
|
83
|
+
selector: 'router-match,[router-match]',
|
|
84
|
+
template: '',
|
|
85
|
+
standalone: true,
|
|
86
|
+
host: {
|
|
87
|
+
'[attr.data-matchId]': 'matchId()',
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
export class RouteMatch {
|
|
91
|
+
matchId = input.required<string>()
|
|
92
|
+
|
|
93
|
+
router = injectRouter()
|
|
94
|
+
|
|
95
|
+
match = computed(() => {
|
|
96
|
+
const matchId = this.matchId()
|
|
97
|
+
return matchId
|
|
98
|
+
? this.router.stores.activeMatchStoresById.get(matchId)?.state
|
|
99
|
+
: undefined
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
matchData = computed(() => {
|
|
103
|
+
const match = this.match()
|
|
104
|
+
if (!match) return null
|
|
105
|
+
|
|
106
|
+
const routeId = match.routeId
|
|
107
|
+
const route = this.router.routesById[routeId] as AnyRoute
|
|
108
|
+
const parentRouteId = route.parentRoute?.id ?? null
|
|
109
|
+
const remountFn =
|
|
110
|
+
route.options.remountDeps ?? this.router.options.defaultRemountDeps
|
|
111
|
+
|
|
112
|
+
const remountDeps = remountFn?.({
|
|
113
|
+
routeId,
|
|
114
|
+
loaderDeps: match.loaderDeps,
|
|
115
|
+
params: match._strictParams,
|
|
116
|
+
search: match._strictSearch,
|
|
117
|
+
})
|
|
118
|
+
const key = remountDeps ? JSON.stringify(remountDeps) : undefined
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
key,
|
|
122
|
+
route,
|
|
123
|
+
match,
|
|
124
|
+
parentRouteId,
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
isFistRouteInRouteTree = computed(
|
|
129
|
+
() => this.matchData()?.parentRouteId === rootRouteId,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
resolvedNoSsr = computed(() => {
|
|
133
|
+
const match = this.matchData()?.match
|
|
134
|
+
if (!match) return true
|
|
135
|
+
return match.ssr === false || match.ssr === 'data-only'
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
shouldClientOnly = computed(() => {
|
|
139
|
+
const match = this.matchData()?.match
|
|
140
|
+
if (!match) return true
|
|
141
|
+
return this.resolvedNoSsr() || !!match._displayPending
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
parentRouteIdSignal = computed(
|
|
145
|
+
() => this.matchData()?.parentRouteId ?? '',
|
|
146
|
+
)
|
|
147
|
+
rootRouteIdSignal = computed(() => rootRouteId)
|
|
148
|
+
|
|
149
|
+
hasPendingMatch = computed(() => {
|
|
150
|
+
const routeId = this.matchData()?.route.id
|
|
151
|
+
return routeId ? Boolean(this.pendingRouteIds()[routeId]) : false
|
|
152
|
+
})
|
|
153
|
+
pendingRouteIds = injectStore(this.router.stores.pendingRouteIds, (ids) => ids)
|
|
154
|
+
nearestMatchContext: NearestMatchContextValue = {
|
|
155
|
+
matchId: this.matchId,
|
|
156
|
+
routeId: computed(() => this.matchData()?.route.id),
|
|
157
|
+
match: this.match,
|
|
158
|
+
hasPending: this.hasPendingMatch,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isCatchingError = injectIsCatchingError({
|
|
162
|
+
matchId: this.matchId,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
render = injectRender(() => {
|
|
166
|
+
const matchData = this.matchData()
|
|
167
|
+
if (!matchData) return null
|
|
168
|
+
|
|
169
|
+
if (this.shouldClientOnly() && this.router.isServer) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { match, route } = matchData
|
|
174
|
+
|
|
175
|
+
if (match.status === 'notFound') {
|
|
176
|
+
const NotFoundComponent = getNotFoundComponent(this.router, route)
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
component: NotFoundComponent,
|
|
180
|
+
}
|
|
181
|
+
} else if (match.status === 'error' || this.isCatchingError()) {
|
|
182
|
+
const RouteErrorComponent =
|
|
183
|
+
getComponent(route.options.errorComponent) ??
|
|
184
|
+
getComponent(this.router.options.defaultErrorComponent)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
component: RouteErrorComponent || null,
|
|
188
|
+
providers: [
|
|
189
|
+
{
|
|
190
|
+
provide: ERROR_STATE_INJECTOR_TOKEN,
|
|
191
|
+
useValue: {
|
|
192
|
+
error: match.error,
|
|
193
|
+
reset: () => {
|
|
194
|
+
this.router.invalidate()
|
|
195
|
+
},
|
|
196
|
+
info: { componentStack: '' },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
} else if (match.status === 'redirected' || match.status === 'pending') {
|
|
202
|
+
const rootShellComponent =
|
|
203
|
+
route.isRoot &&
|
|
204
|
+
(getComponent(route.options.component) ??
|
|
205
|
+
getComponent(this.router.options.defaultComponent))
|
|
206
|
+
|
|
207
|
+
if (rootShellComponent && rootShellComponent !== Outlet) {
|
|
208
|
+
const key = matchData.key
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
key,
|
|
212
|
+
component: rootShellComponent,
|
|
213
|
+
providers: [
|
|
214
|
+
{
|
|
215
|
+
provide: MATCH_CONTEXT_INJECTOR_TOKEN,
|
|
216
|
+
useValue: this.nearestMatchContext,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const PendingComponent =
|
|
223
|
+
getComponent(route.options.pendingComponent) ??
|
|
224
|
+
getComponent(this.router.options.defaultPendingComponent)
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
component: PendingComponent,
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const routeViewComponent =
|
|
231
|
+
getComponent(route.options.component) ??
|
|
232
|
+
getComponent(this.router.options.defaultComponent) ??
|
|
233
|
+
Outlet
|
|
234
|
+
|
|
235
|
+
const key = matchData.key
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
key,
|
|
239
|
+
component: routeViewComponent,
|
|
240
|
+
providers: [
|
|
241
|
+
{
|
|
242
|
+
provide: MATCH_CONTEXT_INJECTOR_TOKEN,
|
|
243
|
+
useValue: this.nearestMatchContext,
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
onRendered = injectOnRendered({
|
|
251
|
+
parentRouteIsRoot: computed(
|
|
252
|
+
() => this.parentRouteIdSignal() === rootRouteId,
|
|
253
|
+
),
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@Component({
|
|
258
|
+
selector: 'outlet,[outlet]',
|
|
259
|
+
template: '',
|
|
260
|
+
standalone: true,
|
|
261
|
+
})
|
|
262
|
+
export class Outlet {
|
|
263
|
+
router = injectRouter()
|
|
264
|
+
nearestMatch = inject(MATCH_CONTEXT_INJECTOR_TOKEN)
|
|
265
|
+
|
|
266
|
+
currentMatch = computed(() => {
|
|
267
|
+
const matchId = this.nearestMatch.matchId()
|
|
268
|
+
return matchId
|
|
269
|
+
? this.router.stores.activeMatchStoresById.get(matchId)?.state
|
|
270
|
+
: undefined
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
routeId = computed(() => this.currentMatch()?.routeId as string)
|
|
274
|
+
|
|
275
|
+
route = computed(
|
|
276
|
+
() => this.router.routesById[this.routeId()] as AnyRoute,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
parentGlobalNotFound = computed(
|
|
280
|
+
() => this.currentMatch()?.globalNotFound ?? false,
|
|
281
|
+
)
|
|
282
|
+
childMatchIdByRouteId = injectStore(
|
|
283
|
+
this.router.stores.childMatchIdByRouteId,
|
|
284
|
+
(value) => value,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
childMatchId = computed(() => {
|
|
288
|
+
const routeId = this.routeId()
|
|
289
|
+
if (!routeId) return null
|
|
290
|
+
return this.childMatchIdByRouteId()[routeId] ?? null
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
render = injectRender(() => {
|
|
294
|
+
if (this.parentGlobalNotFound()) {
|
|
295
|
+
const NotFoundComponent = getNotFoundComponent(this.router, this.route())
|
|
296
|
+
return { component: NotFoundComponent }
|
|
297
|
+
}
|
|
298
|
+
const childMatchId = this.childMatchId()
|
|
299
|
+
|
|
300
|
+
if (!childMatchId) {
|
|
301
|
+
return null
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
component: RouteMatch,
|
|
306
|
+
inputs: {
|
|
307
|
+
matchId: () => this.childMatchId(),
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
type CalledIfFunction<T> = T extends (...args: Array<any>) => any ? ReturnType<T> : T
|
|
314
|
+
|
|
315
|
+
function getComponent<T>(routeComponent: T): CalledIfFunction<T> {
|
|
316
|
+
if (typeof routeComponent === 'function') {
|
|
317
|
+
return routeComponent()
|
|
318
|
+
}
|
|
319
|
+
return routeComponent as any
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getNotFoundComponent(router: AnyRouter, route: AnyRoute) {
|
|
323
|
+
const NotFoundComponent =
|
|
324
|
+
getComponent(route.options.notFoundComponent) ??
|
|
325
|
+
getComponent(router.options.defaultNotFoundComponent)
|
|
326
|
+
|
|
327
|
+
if (NotFoundComponent) {
|
|
328
|
+
return NotFoundComponent
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (isDevMode() && !route.options.notFoundComponent) {
|
|
332
|
+
console.warn(
|
|
333
|
+
`A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<p>Page not found</p>)`,
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return DefaultNotFoundComponent
|
|
338
|
+
}
|
package/src/Matches.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Component } from '@angular/core'
|
|
2
|
+
import { injectRouter } from './injectRouter'
|
|
3
|
+
import { injectStore } from './injectStore'
|
|
4
|
+
import { injectRender } from './renderer/injectRender'
|
|
5
|
+
import { RouteMatch } from './Match'
|
|
6
|
+
import { injectSsrScrollRestorationScript } from './ssr-scroll-restoration'
|
|
7
|
+
import { injectTransitionerSetup } from './transitioner'
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'router-matches',
|
|
11
|
+
template: '',
|
|
12
|
+
standalone: true,
|
|
13
|
+
})
|
|
14
|
+
export class Matches {
|
|
15
|
+
router = injectRouter()
|
|
16
|
+
|
|
17
|
+
private matchId = injectStore(this.router.stores.firstMatchId, (id) => id)
|
|
18
|
+
|
|
19
|
+
private ssrScrollRestoration = injectSsrScrollRestorationScript()
|
|
20
|
+
|
|
21
|
+
transitioner = injectTransitionerSetup()
|
|
22
|
+
|
|
23
|
+
render = injectRender(() => {
|
|
24
|
+
const matchId = this.matchId()
|
|
25
|
+
|
|
26
|
+
if (!matchId) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
component: RouteMatch,
|
|
32
|
+
inputs: {
|
|
33
|
+
matchId: () => matchId,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
EnvironmentInjector,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
computed,
|
|
6
|
+
effect,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
untracked,
|
|
10
|
+
} from '@angular/core'
|
|
11
|
+
import {
|
|
12
|
+
AnyRouter,
|
|
13
|
+
RegisteredRouter,
|
|
14
|
+
RouterOptions,
|
|
15
|
+
} from '@benjavicente/router-core'
|
|
16
|
+
import { Matches } from './Matches'
|
|
17
|
+
import { injectRender } from './renderer/injectRender'
|
|
18
|
+
import { getRouterInjectionKey } from './routerInjectionToken'
|
|
19
|
+
import type { InputSignal } from '@angular/core'
|
|
20
|
+
|
|
21
|
+
const CONTEXT_INPUT_INJECTION_KEY = new InjectionToken<RouterInputs['context']>(
|
|
22
|
+
'CONTEXT',
|
|
23
|
+
{
|
|
24
|
+
providedIn: 'root',
|
|
25
|
+
factory: () => ({}),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const OPTIONS_INPUT_INJECTION_KEY = new InjectionToken<
|
|
30
|
+
Omit<RouterInputs, 'router' | 'context'>
|
|
31
|
+
>('OPTIONS', {
|
|
32
|
+
providedIn: 'root',
|
|
33
|
+
factory: () => ({}),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export type TanstackRouterProviderOptions = {
|
|
37
|
+
router: AnyRouter
|
|
38
|
+
context?: RouterInputs['context']
|
|
39
|
+
options?: Omit<RouterInputs, 'router' | 'context'>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mergeContextWithInject(
|
|
43
|
+
context: RouterInputs['context'],
|
|
44
|
+
): RouterInputs['context'] {
|
|
45
|
+
const environmentInjector = inject(EnvironmentInjector)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
inject: environmentInjector.get.bind(environmentInjector),
|
|
49
|
+
...context,
|
|
50
|
+
} as RouterInputs['context']
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function provideTanstackRouter({
|
|
54
|
+
router,
|
|
55
|
+
context,
|
|
56
|
+
options,
|
|
57
|
+
}: TanstackRouterProviderOptions) {
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
provide: getRouterInjectionKey(),
|
|
61
|
+
useValue: router,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
provide: CONTEXT_INPUT_INJECTION_KEY,
|
|
65
|
+
useFactory: () => mergeContextWithInject(context ?? {}),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
provide: OPTIONS_INPUT_INJECTION_KEY,
|
|
69
|
+
useValue: options ?? {},
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Component({
|
|
75
|
+
selector: 'router-provider,[router-provider]',
|
|
76
|
+
template: '',
|
|
77
|
+
standalone: true,
|
|
78
|
+
})
|
|
79
|
+
export class RouterProvider<TRouter extends AnyRouter = RegisteredRouter> {
|
|
80
|
+
readonly injectedContext: RouterInputs<TRouter>['context'] = inject(
|
|
81
|
+
CONTEXT_INPUT_INJECTION_KEY,
|
|
82
|
+
)
|
|
83
|
+
readonly injectedOptions: Omit<RouterInputs<TRouter>, 'router' | 'context'> =
|
|
84
|
+
inject(OPTIONS_INPUT_INJECTION_KEY)
|
|
85
|
+
readonly injectedRouter: AnyRouter | null = inject(getRouterInjectionKey(), {
|
|
86
|
+
optional: true,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
readonly context: InputSignal<RouterInputs<TRouter>['context']> = input<
|
|
90
|
+
RouterInputs<TRouter>['context']
|
|
91
|
+
>(this.injectedContext)
|
|
92
|
+
readonly options: InputSignal<
|
|
93
|
+
Omit<RouterInputs<TRouter>, 'router' | 'context'>
|
|
94
|
+
> = input<Omit<RouterInputs<TRouter>, 'router' | 'context'>>(
|
|
95
|
+
this.injectedOptions,
|
|
96
|
+
)
|
|
97
|
+
readonly routerInput = input<TRouter | undefined>(undefined, {
|
|
98
|
+
alias: 'router',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
readonly router = computed(() => {
|
|
102
|
+
const inputRouter = this.routerInput()
|
|
103
|
+
if (inputRouter) return inputRouter
|
|
104
|
+
if (this.injectedRouter) return this.injectedRouter as TRouter
|
|
105
|
+
throw new Error(
|
|
106
|
+
'No router provided to <router-provider>. Provide a router with provideTanstackRouter or the router input',
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
readonly updateRouter = effect(() => {
|
|
111
|
+
const router = this.router()
|
|
112
|
+
const context = this.context()
|
|
113
|
+
const options = this.options()
|
|
114
|
+
|
|
115
|
+
router.update({
|
|
116
|
+
...router.options,
|
|
117
|
+
...options,
|
|
118
|
+
context: {
|
|
119
|
+
...router.options.context,
|
|
120
|
+
...context,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
readonly render: ReturnType<typeof injectRender> = injectRender(() => {
|
|
126
|
+
const router = untracked(this.router)
|
|
127
|
+
return {
|
|
128
|
+
component: Matches,
|
|
129
|
+
providers: [
|
|
130
|
+
{
|
|
131
|
+
provide: getRouterInjectionKey(),
|
|
132
|
+
useValue: router,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type RouterInputs<
|
|
140
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
141
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
142
|
+
> = Omit<
|
|
143
|
+
RouterOptions<
|
|
144
|
+
TRouter['routeTree'],
|
|
145
|
+
NonNullable<TRouter['options']['trailingSlash']>,
|
|
146
|
+
false,
|
|
147
|
+
TRouter['history'],
|
|
148
|
+
TDehydrated
|
|
149
|
+
>,
|
|
150
|
+
'context'
|
|
151
|
+
> & {
|
|
152
|
+
router: TRouter
|
|
153
|
+
context?: Partial<
|
|
154
|
+
RouterOptions<
|
|
155
|
+
TRouter['routeTree'],
|
|
156
|
+
NonNullable<TRouter['options']['trailingSlash']>,
|
|
157
|
+
false,
|
|
158
|
+
TRouter['history'],
|
|
159
|
+
TDehydrated
|
|
160
|
+
>['context']
|
|
161
|
+
>
|
|
162
|
+
}
|