@benjavicente/angular-router-experimental 1.142.11 → 1.142.12
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/fesm2022/tanstack-angular-router-experimental-experimental.mjs +192 -187
- package/dist/fesm2022/tanstack-angular-router-experimental.mjs +1145 -1160
- package/dist/types/tanstack-angular-router-experimental-experimental.d.ts +39 -35
- package/dist/types/tanstack-angular-router-experimental.d.ts +56 -42
- package/package.json +5 -4
- package/src/Match.ts +37 -22
- package/src/Matches.ts +5 -2
- package/src/RouterProvider.ts +3 -0
- package/src/document/build-match-managed-document.ts +1 -1
- package/src/document/install-unified-document-sync.ts +6 -3
- package/src/document/provide-tanstack-body-managed-tags.ts +2 -2
- package/src/document/provide-tanstack-document-title.ts +4 -5
- package/src/document/provide-tanstack-head-managed-tags.ts +2 -2
- package/src/index.ts +5 -2
- package/src/injectCanGoBack.ts +2 -2
- package/src/injectLocation.ts +11 -8
- package/src/injectMatch.ts +45 -34
- package/src/injectMatchRoute.ts +4 -5
- package/src/injectMatches.ts +9 -9
- package/src/injectRouterState.ts +15 -10
- package/src/routerStores.ts +25 -52
- package/src/store/injectSelector.ts +62 -0
- package/src/store/injectStore.ts +33 -0
- package/src/transitioner.ts +12 -25
- package/src/injectStore.ts +0 -87
package/src/injectMatch.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as Angular from '@angular/core'
|
|
|
2
2
|
import { deepEqual, invariant } from '@benjavicente/router-core'
|
|
3
3
|
import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
|
|
4
4
|
import { injectRouter } from './injectRouter'
|
|
5
|
-
import { injectStore } from './injectStore'
|
|
5
|
+
import { injectStore } from './store/injectStore'
|
|
6
6
|
import type {
|
|
7
7
|
AnyRouter,
|
|
8
8
|
MakeRouteMatch,
|
|
@@ -13,6 +13,11 @@ import type {
|
|
|
13
13
|
ThrowOrOptional,
|
|
14
14
|
} from '@benjavicente/router-core'
|
|
15
15
|
|
|
16
|
+
const dummyStore = {
|
|
17
|
+
get: () => undefined,
|
|
18
|
+
subscribe: () => ({ unsubscribe: () => {} }),
|
|
19
|
+
} as any
|
|
20
|
+
|
|
16
21
|
export interface InjectMatchBaseOptions<
|
|
17
22
|
TRouter extends AnyRouter,
|
|
18
23
|
TFrom,
|
|
@@ -49,8 +54,8 @@ export type InjectMatchResult<
|
|
|
49
54
|
TSelected,
|
|
50
55
|
> = unknown extends TSelected
|
|
51
56
|
? TStrict extends true
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
? MakeRouteMatch<TRouter['routeTree'], TFrom, TStrict>
|
|
58
|
+
: MakeRouteMatchUnion<TRouter>
|
|
54
59
|
: TSelected
|
|
55
60
|
|
|
56
61
|
export function injectMatch<
|
|
@@ -75,48 +80,54 @@ export function injectMatch<
|
|
|
75
80
|
? undefined
|
|
76
81
|
: Angular.inject(MATCH_CONTEXT_INJECTOR_TOKEN)
|
|
77
82
|
|
|
83
|
+
const match = injectStore(
|
|
84
|
+
opts.from
|
|
85
|
+
? router.stores.getRouteMatchStore(opts.from)
|
|
86
|
+
: () => {
|
|
87
|
+
const matchId = nearestMatch?.matchId()
|
|
88
|
+
return matchId
|
|
89
|
+
? (router.stores.matchStores.get(matchId) ?? dummyStore)
|
|
90
|
+
: dummyStore
|
|
91
|
+
},
|
|
92
|
+
(d) => d,
|
|
93
|
+
)
|
|
78
94
|
const pendingRouteIds = injectStore(
|
|
79
95
|
router.stores.pendingRouteIds,
|
|
80
|
-
(
|
|
96
|
+
(ids) => ids,
|
|
81
97
|
)
|
|
82
98
|
const isTransitioning = injectStore(
|
|
83
99
|
router.stores.isTransitioning,
|
|
84
|
-
(
|
|
100
|
+
(value) => value,
|
|
85
101
|
)
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return nearestMatch?.match()
|
|
93
|
-
}
|
|
103
|
+
return Angular.computed(
|
|
104
|
+
() => {
|
|
105
|
+
const selectedMatch = match()
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
if (selectedMatch !== undefined) {
|
|
108
|
+
return opts.select ? opts.select(selectedMatch as any) : selectedMatch
|
|
109
|
+
}
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
const hasPendingMatch = opts.from
|
|
112
|
+
? Boolean(pendingRouteIds()[opts.from!])
|
|
113
|
+
: (nearestMatch?.hasPending() ?? false)
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
if (
|
|
116
|
+
!hasPendingMatch &&
|
|
117
|
+
!isTransitioning() &&
|
|
118
|
+
(opts.shouldThrow ?? true)
|
|
119
|
+
) {
|
|
120
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
!hasPendingMatch &&
|
|
108
|
-
!isTransitioning() &&
|
|
109
|
-
(opts.shouldThrow ?? true)
|
|
110
|
-
) {
|
|
111
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
112
|
-
throw new Error(
|
|
113
|
-
`Invariant failed: Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
|
|
114
|
-
)
|
|
126
|
+
invariant()
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}, { equal: deepEqual }) as any
|
|
129
|
+
return undefined
|
|
130
|
+
},
|
|
131
|
+
{ equal: deepEqual },
|
|
132
|
+
) as any
|
|
122
133
|
}
|
package/src/injectMatchRoute.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Angular from '@angular/core'
|
|
2
2
|
import { injectRouter } from './injectRouter'
|
|
3
|
-
import { injectStore } from './injectStore'
|
|
3
|
+
import { injectStore } from './store/injectStore'
|
|
4
4
|
import type {
|
|
5
5
|
AnyRouter,
|
|
6
6
|
DeepPartial,
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
MakeOptionalSearchParams,
|
|
10
10
|
MaskOptions,
|
|
11
11
|
MatchRouteOptions,
|
|
12
|
-
NoInfer,
|
|
13
12
|
RegisteredRouter,
|
|
14
13
|
ResolveRoute,
|
|
15
14
|
ToSubOptionsProps,
|
|
@@ -31,7 +30,7 @@ export function injectMatchRoute<
|
|
|
31
30
|
TRouter extends AnyRouter = RegisteredRouter,
|
|
32
31
|
>() {
|
|
33
32
|
const router = injectRouter<TRouter>()
|
|
34
|
-
const reactivity = injectStore(router.stores.
|
|
33
|
+
const reactivity = injectStore(router.stores.matchRouteDeps, (d) => d)
|
|
35
34
|
|
|
36
35
|
return <
|
|
37
36
|
const TFrom extends string = string,
|
|
@@ -41,12 +40,12 @@ export function injectMatchRoute<
|
|
|
41
40
|
>(
|
|
42
41
|
opts: InjectMatchRouteOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
|
|
43
42
|
): Angular.Signal<
|
|
44
|
-
false | Expand<ResolveRoute<TRouter, TFrom,
|
|
43
|
+
false | Expand<ResolveRoute<TRouter, TFrom, TTo>['types']['allParams']>
|
|
45
44
|
> => {
|
|
46
45
|
return Angular.computed(() => {
|
|
47
|
-
reactivity()
|
|
48
46
|
const { pending, caseSensitive, fuzzy, includeSearch, ...rest } = opts
|
|
49
47
|
|
|
48
|
+
reactivity()
|
|
50
49
|
return router.matchRoute(rest as any, {
|
|
51
50
|
pending,
|
|
52
51
|
caseSensitive,
|
package/src/injectMatches.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Angular from '@angular/core'
|
|
2
2
|
import { deepEqual } from '@benjavicente/router-core'
|
|
3
3
|
import { injectRouter } from './injectRouter'
|
|
4
|
-
import { injectStore } from './injectStore'
|
|
4
|
+
import { injectStore } from './store/injectStore'
|
|
5
5
|
import { MATCH_CONTEXT_INJECTOR_TOKEN } from './matchInjectorToken'
|
|
6
6
|
import type {
|
|
7
7
|
AnyRouter,
|
|
@@ -28,15 +28,15 @@ export function injectMatches<
|
|
|
28
28
|
opts?: InjectMatchesBaseOptions<TRouter, TSelected>,
|
|
29
29
|
): Angular.Signal<InjectMatchesResult<TRouter, TSelected>> {
|
|
30
30
|
const router = injectRouter<TRouter>()
|
|
31
|
-
const matches = injectStore(router.stores.activeMatchesSnapshot, (value) => {
|
|
32
|
-
return value as Array<MakeRouteMatchUnion<TRouter>>
|
|
33
|
-
})
|
|
34
31
|
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
return injectStore(
|
|
33
|
+
router.stores.matches,
|
|
34
|
+
(currentMatches) => {
|
|
35
|
+
const matches = currentMatches as Array<MakeRouteMatchUnion<TRouter>>
|
|
36
|
+
return opts?.select ? opts.select(matches) : matches
|
|
37
|
+
},
|
|
38
|
+
{ equal: deepEqual },
|
|
39
|
+
) as Angular.Signal<InjectMatchesResult<TRouter, TSelected>>
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function injectParentMatches<
|
package/src/injectRouterState.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { isServer } from '@benjavicente/router-core/isServer'
|
|
|
2
2
|
import * as Angular from '@angular/core'
|
|
3
3
|
import { deepEqual } from '@benjavicente/router-core'
|
|
4
4
|
import { injectRouter } from './injectRouter'
|
|
5
|
-
import { injectStore } from './injectStore'
|
|
5
|
+
import { injectStore } from './store/injectStore'
|
|
6
6
|
import type {
|
|
7
7
|
AnyRouter,
|
|
8
8
|
RegisteredRouter,
|
|
@@ -29,25 +29,30 @@ export function injectRouterState<
|
|
|
29
29
|
warn: opts?.router === undefined,
|
|
30
30
|
})
|
|
31
31
|
const router = opts?.router ?? contextRouter
|
|
32
|
-
const state = injectStore(router.stores.__store, (state) => state)
|
|
33
32
|
|
|
34
33
|
// During SSR we render exactly once and do not need reactivity.
|
|
35
34
|
// Avoid subscribing to the store on the server since the server store
|
|
36
35
|
// implementation does not provide subscribe() semantics.
|
|
37
|
-
const _isServer =
|
|
38
|
-
typeof isServer === 'boolean' ? isServer : router.isServer
|
|
36
|
+
const _isServer = isServer ?? router.isServer
|
|
39
37
|
if (_isServer) {
|
|
40
|
-
const state = router.stores.__store.
|
|
38
|
+
const state = router.stores.__store.get() as RouterState<
|
|
41
39
|
TRouter['routeTree']
|
|
42
40
|
>
|
|
43
41
|
const selected = (
|
|
44
42
|
opts?.select ? opts.select(state) : state
|
|
45
43
|
) as InjectRouterStateResult<TRouter, TSelected>
|
|
46
|
-
return (() => selected) as Angular.Signal<
|
|
44
|
+
return (() => selected) as Angular.Signal<
|
|
45
|
+
InjectRouterStateResult<TRouter, TSelected>
|
|
46
|
+
>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!opts?.select) {
|
|
50
|
+
return injectStore(router.stores.__store) as Angular.Signal<
|
|
51
|
+
InjectRouterStateResult<TRouter, TSelected>
|
|
52
|
+
>
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}, { equal: deepEqual }) as any;
|
|
55
|
+
return injectStore(router.stores.__store, opts.select, {
|
|
56
|
+
equal: deepEqual,
|
|
57
|
+
}) as Angular.Signal<InjectRouterStateResult<TRouter, TSelected>>
|
|
53
58
|
}
|
package/src/routerStores.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { batch, createAtom } from '@tanstack/store'
|
|
2
2
|
import {
|
|
3
3
|
createNonReactiveMutableStore,
|
|
4
4
|
createNonReactiveReadonlyStore,
|
|
5
5
|
} from '@benjavicente/router-core'
|
|
6
6
|
import { isServer } from '@benjavicente/router-core/isServer'
|
|
7
|
+
import type { Readable } from '@tanstack/store'
|
|
7
8
|
import type {
|
|
8
9
|
AnyRoute,
|
|
9
10
|
GetStoreConfig,
|
|
@@ -13,9 +14,13 @@ import type {
|
|
|
13
14
|
} from '@benjavicente/router-core'
|
|
14
15
|
|
|
15
16
|
declare module '@benjavicente/router-core' {
|
|
17
|
+
export interface RouterReadableStore<TValue> extends Readable<TValue> {}
|
|
18
|
+
|
|
16
19
|
// eslint-disable-next-line unused-imports/no-unused-vars -- generic must match upstream `RouterStores<TRouteTree>` for augmentation
|
|
17
20
|
export interface RouterStores<in out TRouteTree extends AnyRoute> {
|
|
21
|
+
/** Maps each active routeId to the matchId of its child in the match tree. */
|
|
18
22
|
childMatchIdByRouteId: RouterReadableStore<Record<string, string>>
|
|
23
|
+
/** Maps each pending routeId to true for quick lookup. */
|
|
19
24
|
pendingRouteIds: RouterReadableStore<Record<string, boolean>>
|
|
20
25
|
}
|
|
21
26
|
}
|
|
@@ -27,68 +32,32 @@ function initRouterStores(
|
|
|
27
32
|
) => RouterReadableStore<TValue>,
|
|
28
33
|
) {
|
|
29
34
|
stores.childMatchIdByRouteId = createReadonlyStore(() => {
|
|
30
|
-
const ids = stores.matchesId.
|
|
31
|
-
const
|
|
32
|
-
|
|
35
|
+
const ids = stores.matchesId.get()
|
|
36
|
+
const obj: Record<string, string> = {}
|
|
33
37
|
for (let i = 0; i < ids.length - 1; i++) {
|
|
34
|
-
const
|
|
35
|
-
const childId = ids[i + 1]
|
|
36
|
-
if (matchId === undefined || childId === undefined) continue
|
|
37
|
-
const parentStore = stores.activeMatchStoresById.get(matchId)
|
|
38
|
+
const parentStore = stores.matchStores.get(ids[i]!)
|
|
38
39
|
if (parentStore?.routeId) {
|
|
39
|
-
|
|
40
|
+
obj[parentStore.routeId] = ids[i + 1]!
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
return result
|
|
43
|
+
return obj
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
stores.pendingRouteIds = createReadonlyStore(() => {
|
|
47
|
-
const ids = stores.
|
|
48
|
-
const
|
|
49
|
-
|
|
47
|
+
const ids = stores.pendingIds.get()
|
|
48
|
+
const obj: Record<string, boolean> = {}
|
|
50
49
|
for (const id of ids) {
|
|
51
|
-
const store = stores.
|
|
50
|
+
const store = stores.pendingMatchStores.get(id)
|
|
52
51
|
if (store?.routeId) {
|
|
53
|
-
|
|
52
|
+
obj[store.routeId] = true
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
return result
|
|
55
|
+
return obj
|
|
58
56
|
})
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
function createAngularMutableStore<TValue>(
|
|
62
|
-
initialValue: TValue,
|
|
63
|
-
): RouterWritableStore<TValue> {
|
|
64
|
-
const signal = Angular.signal(initialValue)
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
get state() {
|
|
68
|
-
return signal()
|
|
69
|
-
},
|
|
70
|
-
setState(updater) {
|
|
71
|
-
signal.update(updater)
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function createAngularReadonlyStore<TValue>(
|
|
77
|
-
read: () => TValue,
|
|
78
|
-
): RouterReadableStore<TValue> {
|
|
79
|
-
const computed = Angular.computed(read)
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
get state() {
|
|
83
|
-
return computed()
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
59
|
export const getStoreFactory: GetStoreConfig = (opts) => {
|
|
89
|
-
|
|
90
|
-
typeof isServer === 'boolean' ? isServer : !!opts.isServer
|
|
91
|
-
if (useNonReactive) {
|
|
60
|
+
if (isServer ?? opts.isServer) {
|
|
92
61
|
return {
|
|
93
62
|
createMutableStore: createNonReactiveMutableStore,
|
|
94
63
|
createReadonlyStore: createNonReactiveReadonlyStore,
|
|
@@ -99,9 +68,13 @@ export const getStoreFactory: GetStoreConfig = (opts) => {
|
|
|
99
68
|
}
|
|
100
69
|
|
|
101
70
|
return {
|
|
102
|
-
createMutableStore:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
71
|
+
createMutableStore: createAtom as <TValue>(
|
|
72
|
+
initialValue: TValue,
|
|
73
|
+
) => RouterWritableStore<TValue>,
|
|
74
|
+
createReadonlyStore: createAtom as <TValue>(
|
|
75
|
+
read: () => TValue,
|
|
76
|
+
) => RouterReadableStore<TValue>,
|
|
77
|
+
batch,
|
|
78
|
+
init: (stores) => initRouterStores(stores, createAtom),
|
|
106
79
|
}
|
|
107
80
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injector,
|
|
3
|
+
assertInInjectionContext,
|
|
4
|
+
effect,
|
|
5
|
+
inject,
|
|
6
|
+
linkedSignal,
|
|
7
|
+
runInInjectionContext,
|
|
8
|
+
} from '@angular/core'
|
|
9
|
+
import type { CreateSignalOptions, Signal } from '@angular/core'
|
|
10
|
+
|
|
11
|
+
export interface InjectSelectorOptions<TSelected> extends Omit<
|
|
12
|
+
CreateSignalOptions<TSelected>,
|
|
13
|
+
'equal'
|
|
14
|
+
> {
|
|
15
|
+
compare?: (a: TSelected, b: TSelected) => boolean
|
|
16
|
+
injector?: Injector
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type SelectionSource<T> = {
|
|
20
|
+
get: () => T
|
|
21
|
+
subscribe: (listener: (value: T) => void) => {
|
|
22
|
+
unsubscribe: () => void
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveInjector(
|
|
27
|
+
fn: (...args: Array<never>) => unknown,
|
|
28
|
+
injector?: Injector,
|
|
29
|
+
) {
|
|
30
|
+
if (!injector) {
|
|
31
|
+
assertInInjectionContext(fn)
|
|
32
|
+
return inject(Injector)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return injector
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function injectSelector<TState, TSelected = NoInfer<TState>>(
|
|
39
|
+
source: SelectionSource<TState> | (() => SelectionSource<TState>),
|
|
40
|
+
selector: (state: NoInfer<TState>) => TSelected = (d) =>
|
|
41
|
+
d as unknown as TSelected,
|
|
42
|
+
options?: InjectSelectorOptions<TSelected>,
|
|
43
|
+
): Signal<TSelected> {
|
|
44
|
+
const injector = resolveInjector(injectSelector, options?.injector)
|
|
45
|
+
|
|
46
|
+
return runInInjectionContext(injector, () => {
|
|
47
|
+
const _source = typeof source === 'function' ? source : () => source
|
|
48
|
+
|
|
49
|
+
const slice = linkedSignal(() => selector(_source().get()), {
|
|
50
|
+
equal: options?.compare,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
effect((onCleanup) => {
|
|
54
|
+
const { unsubscribe } = _source().subscribe((state) => {
|
|
55
|
+
slice.set(selector(state))
|
|
56
|
+
})
|
|
57
|
+
onCleanup(unsubscribe)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return slice.asReadonly()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { injectSelector } from './injectSelector'
|
|
2
|
+
import type { CreateSignalOptions, Injector, Signal } from '@angular/core'
|
|
3
|
+
import type { SelectionSource } from './injectSelector'
|
|
4
|
+
|
|
5
|
+
type CompatibilityInjectStoreOptions<TSelected> =
|
|
6
|
+
CreateSignalOptions<TSelected> & {
|
|
7
|
+
injector?: Injector
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function injectStore<TState, TSelected = NoInfer<TState>>(
|
|
11
|
+
store: SelectionSource<TState>,
|
|
12
|
+
selector?: (state: NoInfer<TState>) => TSelected,
|
|
13
|
+
options?: CompatibilityInjectStoreOptions<TSelected>,
|
|
14
|
+
): Signal<TSelected>
|
|
15
|
+
export function injectStore<TState, TSelected = NoInfer<TState>>(
|
|
16
|
+
store: SelectionSource<TState> | (() => SelectionSource<TState>),
|
|
17
|
+
selector?: (state: NoInfer<TState>) => TSelected,
|
|
18
|
+
options?: CompatibilityInjectStoreOptions<TSelected>,
|
|
19
|
+
): Signal<TSelected>
|
|
20
|
+
export function injectStore<TState, TSelected = NoInfer<TState>>(
|
|
21
|
+
store: SelectionSource<TState> | (() => SelectionSource<TState>),
|
|
22
|
+
selector: (state: NoInfer<TState>) => TSelected = (d) =>
|
|
23
|
+
d as unknown as TSelected,
|
|
24
|
+
options?: CompatibilityInjectStoreOptions<TSelected>,
|
|
25
|
+
): Signal<TSelected> {
|
|
26
|
+
const { equal, injector, ...signalOptions } = options ?? {}
|
|
27
|
+
|
|
28
|
+
return injectSelector(store, selector, {
|
|
29
|
+
...signalOptions,
|
|
30
|
+
compare: equal,
|
|
31
|
+
injector,
|
|
32
|
+
})
|
|
33
|
+
}
|
package/src/transitioner.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
trimPathRight,
|
|
6
6
|
} from '@benjavicente/router-core'
|
|
7
7
|
import { injectRouter } from './injectRouter'
|
|
8
|
-
import { injectStore } from './injectStore'
|
|
8
|
+
import { injectStore } from './store/injectStore'
|
|
9
9
|
import type { AnyRouter } from '@benjavicente/router-core'
|
|
10
10
|
|
|
11
11
|
// Track mount state per router to avoid double-loading
|
|
@@ -46,7 +46,7 @@ export function injectTransitionerSetup() {
|
|
|
46
46
|
|
|
47
47
|
// Track pending state changes
|
|
48
48
|
const hasPendingMatches = injectStore(
|
|
49
|
-
router.stores.
|
|
49
|
+
router.stores.hasPending,
|
|
50
50
|
(value) => value,
|
|
51
51
|
)
|
|
52
52
|
const status = injectStore(router.stores.status, (value) => value)
|
|
@@ -72,7 +72,7 @@ export function injectTransitionerSetup() {
|
|
|
72
72
|
// Implement startTransition similar to React/Solid
|
|
73
73
|
// Angular doesn't have a native startTransition like React 18, so we simulate it
|
|
74
74
|
router.startTransition = (fn: () => void | Promise<void>) => {
|
|
75
|
-
router.stores.isTransitioning.
|
|
75
|
+
router.stores.isTransitioning.set(true)
|
|
76
76
|
|
|
77
77
|
// Helper to end the transition
|
|
78
78
|
const endTransition = () => {
|
|
@@ -84,7 +84,7 @@ export function injectTransitionerSetup() {
|
|
|
84
84
|
{
|
|
85
85
|
read: () => {
|
|
86
86
|
try {
|
|
87
|
-
router.stores.isTransitioning.
|
|
87
|
+
router.stores.isTransitioning.set(false)
|
|
88
88
|
} catch {
|
|
89
89
|
// Ignore errors if component is unmounted
|
|
90
90
|
}
|
|
@@ -133,8 +133,8 @@ export function injectTransitionerSetup() {
|
|
|
133
133
|
isMounted.set(true)
|
|
134
134
|
if (!isAnyPending()) {
|
|
135
135
|
if (status() === 'pending') {
|
|
136
|
-
router.stores.status.
|
|
137
|
-
router.stores.resolvedLocation.
|
|
136
|
+
router.stores.status.set('idle')
|
|
137
|
+
router.stores.resolvedLocation.set(location())
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
})
|
|
@@ -176,10 +176,7 @@ export function injectTransitionerSetup() {
|
|
|
176
176
|
if (prevIsLoading() && !isLoading()) {
|
|
177
177
|
router.emit({
|
|
178
178
|
type: 'onLoad',
|
|
179
|
-
...getLocationChangeInfo(
|
|
180
|
-
location(),
|
|
181
|
-
resolvedLocation(),
|
|
182
|
-
),
|
|
179
|
+
...getLocationChangeInfo(location(), resolvedLocation()),
|
|
183
180
|
})
|
|
184
181
|
}
|
|
185
182
|
} catch {
|
|
@@ -194,10 +191,7 @@ export function injectTransitionerSetup() {
|
|
|
194
191
|
if (prevIsPagePending() && !isPagePending()) {
|
|
195
192
|
router.emit({
|
|
196
193
|
type: 'onBeforeRouteMount',
|
|
197
|
-
...getLocationChangeInfo(
|
|
198
|
-
location(),
|
|
199
|
-
resolvedLocation(),
|
|
200
|
-
),
|
|
194
|
+
...getLocationChangeInfo(location(), resolvedLocation()),
|
|
201
195
|
})
|
|
202
196
|
}
|
|
203
197
|
} catch {
|
|
@@ -209,21 +203,14 @@ export function injectTransitionerSetup() {
|
|
|
209
203
|
Angular.effect(() => {
|
|
210
204
|
if (!isMounted()) return
|
|
211
205
|
try {
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
status() === 'pending'
|
|
216
|
-
) {
|
|
217
|
-
router.stores.status.setState(() => 'idle')
|
|
218
|
-
router.stores.resolvedLocation.setState(() => location())
|
|
206
|
+
if (prevIsAnyPending() && !isAnyPending() && status() === 'pending') {
|
|
207
|
+
router.stores.status.set('idle')
|
|
208
|
+
router.stores.resolvedLocation.set(location())
|
|
219
209
|
}
|
|
220
210
|
|
|
221
211
|
// The router was pending and now it's not
|
|
222
212
|
if (prevIsAnyPending() && !isAnyPending()) {
|
|
223
|
-
const changeInfo = getLocationChangeInfo(
|
|
224
|
-
location(),
|
|
225
|
-
resolvedLocation(),
|
|
226
|
-
)
|
|
213
|
+
const changeInfo = getLocationChangeInfo(location(), resolvedLocation())
|
|
227
214
|
router.emit({
|
|
228
215
|
type: 'onResolved',
|
|
229
216
|
...changeInfo,
|
package/src/injectStore.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
}
|