@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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertInInjectionContext,
|
|
3
|
+
effect,
|
|
4
|
+
linkedSignal,
|
|
5
|
+
} from '@angular/core'
|
|
6
|
+
import type { CreateSignalOptions, Signal } from '@angular/core'
|
|
7
|
+
|
|
8
|
+
type ReadableStore<TState> = {
|
|
9
|
+
state: TState
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function injectStore<TState, TSelected = NoInfer<TState>>(
|
|
13
|
+
storeOrStoreSignal:
|
|
14
|
+
| ReadableStore<TState>
|
|
15
|
+
| (() => ReadableStore<TState>),
|
|
16
|
+
selector: (state: NoInfer<TState>) => TSelected = (d) =>
|
|
17
|
+
d as unknown as TSelected,
|
|
18
|
+
options: CreateSignalOptions<TSelected> = {
|
|
19
|
+
equal: shallow,
|
|
20
|
+
},
|
|
21
|
+
): Signal<TSelected> {
|
|
22
|
+
assertInInjectionContext(injectStore)
|
|
23
|
+
|
|
24
|
+
const storeSignal =
|
|
25
|
+
typeof storeOrStoreSignal === 'function'
|
|
26
|
+
? storeOrStoreSignal
|
|
27
|
+
: () => storeOrStoreSignal
|
|
28
|
+
|
|
29
|
+
const slice = linkedSignal(() => selector(storeSignal().state), options)
|
|
30
|
+
|
|
31
|
+
effect(() => {
|
|
32
|
+
slice()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return slice.asReadonly()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function shallow<T>(objA: T, objB: T) {
|
|
39
|
+
if (Object.is(objA, objB)) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
typeof objA !== 'object' ||
|
|
45
|
+
objA === null ||
|
|
46
|
+
typeof objB !== 'object' ||
|
|
47
|
+
objB === null
|
|
48
|
+
) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (objA instanceof Map && objB instanceof Map) {
|
|
53
|
+
if (objA.size !== objB.size) return false
|
|
54
|
+
for (const [k, v] of objA) {
|
|
55
|
+
if (!objB.has(k) || !Object.is(v, objB.get(k))) return false
|
|
56
|
+
}
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (objA instanceof Set && objB instanceof Set) {
|
|
61
|
+
if (objA.size !== objB.size) return false
|
|
62
|
+
for (const v of objA) {
|
|
63
|
+
if (!objB.has(v)) return false
|
|
64
|
+
}
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (objA instanceof Date && objB instanceof Date) {
|
|
69
|
+
return objA.getTime() === objB.getTime()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const keysA = Object.keys(objA)
|
|
73
|
+
if (keysA.length !== Object.keys(objB).length) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const key of keysA) {
|
|
78
|
+
if (
|
|
79
|
+
!Object.prototype.hasOwnProperty.call(objB, key) ||
|
|
80
|
+
!Object.is(objA[key as keyof T], objB[key as keyof T])
|
|
81
|
+
) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as Angular from '@angular/core'
|
|
2
|
+
import type { AnyRouteMatch } from '@benjavicente/router-core'
|
|
3
|
+
|
|
4
|
+
export type NearestMatchContextValue = {
|
|
5
|
+
matchId: () => string | undefined,
|
|
6
|
+
routeId: () => string | undefined,
|
|
7
|
+
match: () => AnyRouteMatch | undefined,
|
|
8
|
+
hasPending: () => boolean,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const defaultNearestMatchContext: NearestMatchContextValue = {
|
|
12
|
+
matchId: () => undefined,
|
|
13
|
+
routeId: () => undefined,
|
|
14
|
+
match: () => undefined,
|
|
15
|
+
hasPending: () => false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const MATCH_CONTEXT_INJECTOR_TOKEN = new Angular.InjectionToken<
|
|
19
|
+
NearestMatchContextValue
|
|
20
|
+
>('MATCH_CONTEXT_INJECTOR', {
|
|
21
|
+
providedIn: 'root',
|
|
22
|
+
factory: () => defaultNearestMatchContext,
|
|
23
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as Angular from '@angular/core'
|
|
2
|
+
import { injectRouter } from '../injectRouter'
|
|
3
|
+
import { injectRouterState } from '../injectRouterState'
|
|
4
|
+
import type { AnyRoute } from '@benjavicente/router-core'
|
|
5
|
+
|
|
6
|
+
export function injectIsCatchingError({
|
|
7
|
+
matchId,
|
|
8
|
+
}: {
|
|
9
|
+
matchId: Angular.Signal<string | undefined>
|
|
10
|
+
}): Angular.Signal<boolean> {
|
|
11
|
+
const router = injectRouter()
|
|
12
|
+
|
|
13
|
+
const matches = injectRouterState({
|
|
14
|
+
select: (s) => s.matches,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const matchIndex = Angular.computed(() => {
|
|
18
|
+
return matches().findIndex((m) => m.id === matchId())
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return Angular.computed(() => {
|
|
22
|
+
// The child route will handle the error with the default error component.
|
|
23
|
+
if (router.options.defaultErrorComponent != null) return false;
|
|
24
|
+
|
|
25
|
+
const startingIndex = matchIndex()
|
|
26
|
+
if (startingIndex === -1) return false
|
|
27
|
+
const matchesList = matches()
|
|
28
|
+
|
|
29
|
+
for (let i = startingIndex + 1; i < matchesList.length; i++) {
|
|
30
|
+
const descendant = matchesList[i]
|
|
31
|
+
const route = router.routesById[descendant?.routeId] as AnyRoute
|
|
32
|
+
// Is catched by a child route with an error component.
|
|
33
|
+
if (route.options.errorComponent != null) return false
|
|
34
|
+
|
|
35
|
+
// Found error status without error component in between.
|
|
36
|
+
if (descendant?.status === "error") return true
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DestroyRef,
|
|
3
|
+
Injector,
|
|
4
|
+
ViewContainerRef,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
inputBinding,
|
|
8
|
+
} from '@angular/core'
|
|
9
|
+
import type { Provider, Type } from '@angular/core'
|
|
10
|
+
|
|
11
|
+
export type RenderValue =
|
|
12
|
+
| {
|
|
13
|
+
key?: string
|
|
14
|
+
component: Type<any> | null | undefined
|
|
15
|
+
inputs?: Record<string, () => unknown>
|
|
16
|
+
providers?: Array<Provider>
|
|
17
|
+
}
|
|
18
|
+
| null
|
|
19
|
+
| undefined
|
|
20
|
+
|
|
21
|
+
export function injectRender(renderValueFn: () => RenderValue): void {
|
|
22
|
+
const vcr = inject(ViewContainerRef)
|
|
23
|
+
const parent = inject(Injector)
|
|
24
|
+
|
|
25
|
+
let lastKey: Array<any> = []
|
|
26
|
+
|
|
27
|
+
effect(() => {
|
|
28
|
+
const renderValue = renderValueFn()
|
|
29
|
+
|
|
30
|
+
const newKey = resolvedKey(renderValue)
|
|
31
|
+
if (keysAreEqual(lastKey, newKey)) return
|
|
32
|
+
|
|
33
|
+
if (lastKey.length > 0) vcr.clear()
|
|
34
|
+
|
|
35
|
+
lastKey = newKey
|
|
36
|
+
|
|
37
|
+
const component = renderValue?.component
|
|
38
|
+
if (!component) return
|
|
39
|
+
|
|
40
|
+
const providers = renderValue.providers ?? []
|
|
41
|
+
const childInjector = Injector.create({ providers, parent })
|
|
42
|
+
const bindings = Object.entries(renderValue.inputs ?? {}).map(
|
|
43
|
+
([name, value]) => inputBinding(name, value),
|
|
44
|
+
)
|
|
45
|
+
const cmpRef = vcr.createComponent(component, {
|
|
46
|
+
injector: childInjector,
|
|
47
|
+
bindings,
|
|
48
|
+
})
|
|
49
|
+
cmpRef.changeDetectorRef.markForCheck()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
inject(DestroyRef).onDestroy(() => {
|
|
53
|
+
vcr.clear()
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolvedKey(value: RenderValue) {
|
|
58
|
+
const component = value?.component
|
|
59
|
+
if (!value || !component) return []
|
|
60
|
+
return [component, value.key]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function keysAreEqual(a: Array<any>, b: Array<any>) {
|
|
64
|
+
if (a.length !== b.length) return false
|
|
65
|
+
for (let i = 0; i < a.length; i++) {
|
|
66
|
+
if (a[i] !== b[i]) return false
|
|
67
|
+
}
|
|
68
|
+
return true
|
|
69
|
+
}
|