@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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/fesm2022/tanstack-angular-router-experimental-experimental.mjs +920 -0
  4. package/dist/fesm2022/tanstack-angular-router-experimental.mjs +4131 -0
  5. package/dist/types/tanstack-angular-router-experimental-experimental.d.ts +110 -0
  6. package/dist/types/tanstack-angular-router-experimental.d.ts +733 -0
  7. package/experimental/injectRouteErrorHandler.ts +51 -0
  8. package/experimental/public_api.ts +8 -0
  9. package/package.json +98 -0
  10. package/src/DefaultNotFound.ts +9 -0
  11. package/src/Link.ts +352 -0
  12. package/src/Match.ts +338 -0
  13. package/src/Matches.ts +37 -0
  14. package/src/RouterProvider.ts +162 -0
  15. package/src/document/build-match-managed-document.ts +308 -0
  16. package/src/document/document-dehydration.ts +27 -0
  17. package/src/document/document-equality.ts +29 -0
  18. package/src/document/document-router-token.ts +6 -0
  19. package/src/document/index.ts +33 -0
  20. package/src/document/install-unified-document-sync.ts +108 -0
  21. package/src/document/managed-document-types.ts +36 -0
  22. package/src/document/managed-dom.ts +307 -0
  23. package/src/document/provide-tanstack-body-managed-tags.ts +78 -0
  24. package/src/document/provide-tanstack-document-title.ts +59 -0
  25. package/src/document/provide-tanstack-document.ts +62 -0
  26. package/src/document/provide-tanstack-head-managed-tags.ts +63 -0
  27. package/src/fileRoute.ts +232 -0
  28. package/src/index.ts +173 -0
  29. package/src/injectBlocker.ts +196 -0
  30. package/src/injectCanGoBack.ts +11 -0
  31. package/src/injectErrorState.ts +21 -0
  32. package/src/injectIntersectionObserver.ts +28 -0
  33. package/src/injectLoaderData.ts +49 -0
  34. package/src/injectLoaderDeps.ts +45 -0
  35. package/src/injectLocation.ts +38 -0
  36. package/src/injectMatch.ts +122 -0
  37. package/src/injectMatchRoute.ts +58 -0
  38. package/src/injectMatches.ts +79 -0
  39. package/src/injectNavigate.ts +24 -0
  40. package/src/injectParams.ts +71 -0
  41. package/src/injectRouteContext.ts +31 -0
  42. package/src/injectRouter.ts +17 -0
  43. package/src/injectRouterState.ts +53 -0
  44. package/src/injectSearch.ts +71 -0
  45. package/src/injectStore.ts +87 -0
  46. package/src/matchInjectorToken.ts +23 -0
  47. package/src/renderer/injectIsCatchingError.ts +40 -0
  48. package/src/renderer/injectRender.ts +69 -0
  49. package/src/route.ts +641 -0
  50. package/src/router.ts +141 -0
  51. package/src/routerInjectionToken.ts +24 -0
  52. package/src/routerStores.ts +107 -0
  53. package/src/ssr-scroll-restoration.ts +48 -0
  54. 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
+ }