@typed/router 0.13.0 → 0.14.0
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/Link.d.ts +26 -11
- package/dist/Link.d.ts.map +1 -1
- package/dist/Link.js +47 -23
- package/dist/Link.js.map +1 -1
- package/dist/Match.d.ts +33 -0
- package/dist/Match.d.ts.map +1 -0
- package/dist/Match.js +16 -0
- package/dist/Match.js.map +1 -0
- package/dist/Matcher.d.ts +28 -0
- package/dist/Matcher.d.ts.map +1 -0
- package/dist/Matcher.js +24 -0
- package/dist/Matcher.js.map +1 -0
- package/dist/Navigation.d.ts +10 -0
- package/dist/Navigation.d.ts.map +1 -0
- package/dist/Navigation.js +7 -0
- package/dist/Navigation.js.map +1 -0
- package/dist/Redirect.d.ts +27 -0
- package/dist/Redirect.d.ts.map +1 -0
- package/dist/Redirect.js +17 -0
- package/dist/Redirect.js.map +1 -0
- package/dist/RouteOutlet.d.ts +3 -0
- package/dist/RouteOutlet.d.ts.map +1 -0
- package/dist/RouteOutlet.js +2 -0
- package/dist/RouteOutlet.js.map +1 -0
- package/dist/ScrollRestoration.d.ts +19 -0
- package/dist/ScrollRestoration.d.ts.map +1 -0
- package/dist/ScrollRestoration.js +64 -0
- package/dist/ScrollRestoration.js.map +1 -0
- package/dist/cjs/Link.d.ts +26 -11
- package/dist/cjs/Link.d.ts.map +1 -1
- package/dist/cjs/Link.js +47 -22
- package/dist/cjs/Link.js.map +1 -1
- package/dist/cjs/Match.d.ts +33 -0
- package/dist/cjs/Match.d.ts.map +1 -0
- package/dist/cjs/Match.js +43 -0
- package/dist/cjs/Match.js.map +1 -0
- package/dist/cjs/Matcher.d.ts +28 -0
- package/dist/cjs/Matcher.d.ts.map +1 -0
- package/dist/cjs/Matcher.js +52 -0
- package/dist/cjs/Matcher.js.map +1 -0
- package/dist/cjs/Navigation.d.ts +10 -0
- package/dist/cjs/Navigation.d.ts.map +1 -0
- package/dist/cjs/Navigation.js +34 -0
- package/dist/cjs/Navigation.js.map +1 -0
- package/dist/cjs/Redirect.d.ts +27 -0
- package/dist/cjs/Redirect.d.ts.map +1 -0
- package/dist/cjs/Redirect.js +44 -0
- package/dist/cjs/Redirect.js.map +1 -0
- package/dist/cjs/ScrollRestoration.d.ts +19 -0
- package/dist/cjs/ScrollRestoration.d.ts.map +1 -0
- package/dist/cjs/ScrollRestoration.js +91 -0
- package/dist/cjs/ScrollRestoration.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/matchRoutes.d.ts +8 -0
- package/dist/cjs/matchRoutes.d.ts.map +1 -0
- package/dist/cjs/matchRoutes.js +77 -0
- package/dist/cjs/matchRoutes.js.map +1 -0
- package/dist/cjs/router.d.ts +24 -63
- package/dist/cjs/router.d.ts.map +1 -1
- package/dist/cjs/router.js +22 -159
- package/dist/cjs/router.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/matchRoutes.d.ts +8 -0
- package/dist/matchRoutes.d.ts.map +1 -0
- package/dist/matchRoutes.js +50 -0
- package/dist/matchRoutes.js.map +1 -0
- package/dist/router.d.ts +24 -63
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +19 -153
- package/dist/router.js.map +1 -1
- package/dist/tsconfig.cjs.build.tsbuildinfo +1 -1
- package/package.json +12 -10
- package/project.json +12 -10
- package/src/Link.ts +129 -39
- package/src/Match.ts +114 -0
- package/src/Matcher.ts +139 -0
- package/src/Navigation.ts +24 -0
- package/src/Redirect.ts +21 -0
- package/src/ScrollRestoration.ts +110 -0
- package/src/index.ts +7 -3
- package/src/matchRoutes.ts +112 -0
- package/src/router.ts +56 -311
- package/tsconfig.build.json +5 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.cjs.build.json +6 -0
- package/tsconfig.json +6 -0
- package/vite.config.js +3 -0
- package/src/RouteMatch.ts +0 -56
- package/src/RouteMatcher.ts +0 -264
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { identity, pipe } from '@effect/data/Function'
|
|
2
|
+
import * as Option from '@effect/data/Option'
|
|
3
|
+
import * as Effect from '@effect/io/Effect'
|
|
4
|
+
import * as Scope from '@effect/io/Scope'
|
|
5
|
+
import * as Fx from '@typed/fx'
|
|
6
|
+
import { RenderContext } from '@typed/html'
|
|
7
|
+
import { NavigationError } from '@typed/navigation'
|
|
8
|
+
import { ParamsOf } from '@typed/path'
|
|
9
|
+
|
|
10
|
+
import { Match } from './Match.js'
|
|
11
|
+
import { Redirect } from './Redirect.js'
|
|
12
|
+
import { Router, getCurrentPathFromUrl } from './router.js'
|
|
13
|
+
|
|
14
|
+
export function matchRoutes<
|
|
15
|
+
const Matches extends ReadonlyArray<Match.Any>,
|
|
16
|
+
R = never,
|
|
17
|
+
E = never,
|
|
18
|
+
A = never,
|
|
19
|
+
>(
|
|
20
|
+
matches: Matches,
|
|
21
|
+
onNotFound: (
|
|
22
|
+
params: Fx.Filtered<never, never, Readonly<Record<string, string>>>,
|
|
23
|
+
) => Fx.Fx<R, E, A> = () => Fx.empty(),
|
|
24
|
+
): Fx.Fx<
|
|
25
|
+
Router | RenderContext | Scope.Scope | R | Match.Context<Matches[number]>,
|
|
26
|
+
Exclude<E | Match.Error<Matches[number]>, Redirect>,
|
|
27
|
+
A | Match.Success<Matches[number]>
|
|
28
|
+
> {
|
|
29
|
+
type _R = R | Match.Context<Matches[number]> | RenderContext | Router | Scope.Scope
|
|
30
|
+
type _E = E | Match.Error<Matches[number]> | Redirect
|
|
31
|
+
type _A = A | Match.Success<Matches[number]>
|
|
32
|
+
|
|
33
|
+
type RENDERABLE = Fx.Fx<_R, _E, _A>
|
|
34
|
+
|
|
35
|
+
return Fx.gen(function* ($) {
|
|
36
|
+
const { environment } = yield* $(RenderContext)
|
|
37
|
+
const isBrowser = environment === 'browser'
|
|
38
|
+
const router = yield* $(Router)
|
|
39
|
+
const notFound = onNotFound(router.params) as RENDERABLE
|
|
40
|
+
const matchers = matches.map((match) => {
|
|
41
|
+
const nestedRouter = router.define(match.route)
|
|
42
|
+
const render = pipe(
|
|
43
|
+
nestedRouter.params,
|
|
44
|
+
match.render,
|
|
45
|
+
Fx.provideService(Router, nestedRouter as Router),
|
|
46
|
+
Fx.scoped,
|
|
47
|
+
) as RENDERABLE
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
match.route,
|
|
51
|
+
render,
|
|
52
|
+
match.options?.guard as
|
|
53
|
+
| ((params: ParamsOf<string>) => Effect.Effect<_R, NavigationError, boolean>)
|
|
54
|
+
| undefined,
|
|
55
|
+
match.options?.onMatch as
|
|
56
|
+
| ((params: ParamsOf<string>) => Effect.Effect<_R, _E, unknown>)
|
|
57
|
+
| undefined,
|
|
58
|
+
] as const
|
|
59
|
+
})
|
|
60
|
+
const length = matchers.length
|
|
61
|
+
|
|
62
|
+
const matched = yield* $(
|
|
63
|
+
Fx.makeRef<never, any, RENDERABLE>(Effect.succeed<RENDERABLE>(Fx.empty())),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const matchPath = (path: string) =>
|
|
67
|
+
Effect.gen(function* ($) {
|
|
68
|
+
for (let i = 0; i < length; ++i) {
|
|
69
|
+
const [route, render, guard, onMatch] = matchers[i]
|
|
70
|
+
const params = route.match(path)
|
|
71
|
+
|
|
72
|
+
if (Option.isSome(params)) {
|
|
73
|
+
// If there is a guard and it fails, continue to next route
|
|
74
|
+
if (guard && !(yield* $(guard(params.value)))) {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If there is an onMatch handler, run it and catch any errors
|
|
79
|
+
// This is useful when you want to add tracking when a route is matched
|
|
80
|
+
if (onMatch) {
|
|
81
|
+
yield* $(
|
|
82
|
+
onMatch(params.value),
|
|
83
|
+
Effect.catchAllCause((cause) => matched.error(cause)),
|
|
84
|
+
Effect.forkScoped,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return yield* $(matched.set(render))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return yield* $(matched.set(notFound))
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Match a Route when navigation occurs
|
|
96
|
+
yield* $(
|
|
97
|
+
router.navigation.onNavigation((event) =>
|
|
98
|
+
matchPath(getCurrentPathFromUrl(event.destination.url)),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return pipe(
|
|
103
|
+
matched,
|
|
104
|
+
isBrowser ? identity : Fx.take(1),
|
|
105
|
+
Redirect.switchMatch(
|
|
106
|
+
(r) => Fx.as(Fx.fromEffect(router.navigation.navigate(r.url, r.options)), Option.none()),
|
|
107
|
+
Fx.map(Option.some),
|
|
108
|
+
),
|
|
109
|
+
Fx.compact,
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -1,338 +1,83 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/ban-types */
|
|
2
|
-
import { pipe } from '@effect/data/Function'
|
|
3
1
|
import * as Option from '@effect/data/Option'
|
|
4
2
|
import * as Effect from '@effect/io/Effect'
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import * as Route from '@typed/route'
|
|
15
|
-
|
|
16
|
-
export interface Router<out R = never, out E = never, in out P extends string = string> {
|
|
17
|
-
/**
|
|
18
|
-
* The base route the Router is starting from.
|
|
19
|
-
*/
|
|
20
|
-
readonly route: Route.Route<R, E, P>
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The current path of the application
|
|
24
|
-
*/
|
|
25
|
-
readonly currentPath: Fx.RefSubject<never, string>
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The current matched params of the router
|
|
29
|
-
*/
|
|
30
|
-
readonly params: Fx.Fx<R, E, Path.ParamsOf<P>>
|
|
31
|
-
|
|
3
|
+
import * as Layer from '@effect/io/Layer'
|
|
4
|
+
import { Tag } from '@typed/context'
|
|
5
|
+
import { GlobalThis, Window, DomServices, domServices, localStorage } from '@typed/dom'
|
|
6
|
+
import { Filtered } from '@typed/fx'
|
|
7
|
+
import * as Navigation from '@typed/navigation'
|
|
8
|
+
import { ParamsOf, PathJoin } from '@typed/path'
|
|
9
|
+
import { Route } from '@typed/route'
|
|
10
|
+
|
|
11
|
+
export interface Router<in out P extends string = string> {
|
|
32
12
|
/**
|
|
33
|
-
* The
|
|
13
|
+
* The base Route for this Router instance.
|
|
34
14
|
*/
|
|
35
|
-
readonly
|
|
15
|
+
readonly route: Route<P>
|
|
36
16
|
|
|
37
17
|
/**
|
|
38
|
-
*
|
|
18
|
+
* The current params for the current path.
|
|
39
19
|
*/
|
|
40
|
-
readonly
|
|
41
|
-
route: R2,
|
|
42
|
-
...[params]: [keyof P] extends [never] ? [] : [P]
|
|
43
|
-
) => Effect.Effect<
|
|
44
|
-
R,
|
|
45
|
-
never,
|
|
46
|
-
Path.PathJoin<
|
|
47
|
-
[Path.Interpolate<Route.PathOf<R>, Route.ParamsOf<R>>, Path.Interpolate<Route.PathOf<R2>, P>]
|
|
48
|
-
>
|
|
49
|
-
>
|
|
20
|
+
readonly params: Filtered<never, never, ParamsOf<P>>
|
|
50
21
|
|
|
51
22
|
/**
|
|
52
|
-
*
|
|
23
|
+
* Construct a new Router instance by defining a new Route which is concatenated
|
|
24
|
+
* to the current Route.
|
|
53
25
|
*/
|
|
54
|
-
readonly define: <
|
|
55
|
-
route: Route.Route<R2, E2, Path2>,
|
|
56
|
-
) => Router<R | R2, E | E2, Path.PathJoin<[P, Path2]>>
|
|
26
|
+
readonly define: <P2 extends string>(route: Route<P2>) => Router<PathJoin<[P, P2]>>
|
|
57
27
|
|
|
58
28
|
/**
|
|
59
|
-
* The parent
|
|
29
|
+
* The parent Router instance if one exists.
|
|
60
30
|
*/
|
|
61
|
-
readonly parent: Option.Option<Router<
|
|
31
|
+
readonly parent: Option.Option<Router<string>>
|
|
62
32
|
|
|
63
33
|
/**
|
|
64
|
-
*
|
|
34
|
+
* The Navigation Service
|
|
65
35
|
*/
|
|
66
|
-
readonly
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const Router = Object.assign(Context.Tag<Router>('@typed/router/Router'), {
|
|
70
|
-
make: function makeRouter<R = never, E = never, P extends string = string>(
|
|
71
|
-
route: Route.Route<R, E, P>,
|
|
72
|
-
currentPath: Fx.RefSubject<never, string>,
|
|
73
|
-
parent: Option.Option<Router<any, any, string>> = Option.none(),
|
|
74
|
-
): Router<R, E, P> {
|
|
75
|
-
const outlet = Fx.RefSubject.unsafeMake<Redirect, html.Renderable>(
|
|
76
|
-
Effect.sync((): html.Renderable => null),
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const createPath = <R2 extends Route.Route<any, any, string>, P extends Route.ParamsOf<R2>>(
|
|
80
|
-
other: R2,
|
|
81
|
-
...[params]: [keyof P] extends [never] ? [] : [P]
|
|
82
|
-
): Effect.Effect<
|
|
83
|
-
R,
|
|
84
|
-
E,
|
|
85
|
-
Path.PathJoin<
|
|
86
|
-
[
|
|
87
|
-
Path.Interpolate<Route.PathOf<R>, Route.ParamsOf<R>>,
|
|
88
|
-
Path.Interpolate<Route.PathOf<R2>, P>,
|
|
89
|
-
]
|
|
90
|
-
>
|
|
91
|
-
> =>
|
|
92
|
-
Effect.gen(function* ($) {
|
|
93
|
-
const path = yield* $(currentPath.get)
|
|
94
|
-
const baseParams = yield* $(route.match(path))
|
|
95
|
-
|
|
96
|
-
if (Option.isNone(baseParams)) {
|
|
97
|
-
return yield* $(
|
|
98
|
-
Effect.dieMessage(
|
|
99
|
-
`Can not create path when the parent can not be matched.
|
|
100
|
-
Parent Route: ${route.path}
|
|
101
|
-
Current Route: ${other.path}
|
|
102
|
-
Current Path: ${path}`,
|
|
103
|
-
),
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return route.concat(other).make({ ...baseParams.value, ...params } as any) as any
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
const router: Router<R, E, P> = {
|
|
111
|
-
route,
|
|
112
|
-
currentPath,
|
|
113
|
-
params: pipe(currentPath, Fx.switchMapEffect(route.match), Fx.compact, Fx.hold),
|
|
114
|
-
outlet,
|
|
115
|
-
createPath: createPath as Router<R, P>['createPath'],
|
|
116
|
-
define: <R2, E2, Path2 extends string>(other: Route.Route<R2, E2, Path2>) =>
|
|
117
|
-
makeRouter(route.concat(other), currentPath, Option.some(router as any)) as any,
|
|
118
|
-
provideContext: (env) => provideContext(env)(router),
|
|
119
|
-
parent,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return router
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
export const outlet: Fx.Fx<RenderContext | Router, Redirect, html.Renderable> =
|
|
127
|
-
RenderContext.withFx(({ environment }) =>
|
|
128
|
-
Router.withFx((r) =>
|
|
129
|
-
environment === 'browser'
|
|
130
|
-
? r.outlet
|
|
131
|
-
: pipe(
|
|
132
|
-
r.outlet,
|
|
133
|
-
Fx.skipUntil((x) => x !== null),
|
|
134
|
-
Fx.take(1),
|
|
135
|
-
),
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
export const currentPath: Fx.Fx<Router, never, string> = Router.withFx((r) => r.currentPath)
|
|
140
|
-
|
|
141
|
-
export function provideContext<R>(environment: Context.Context<R>) {
|
|
142
|
-
return <E, P extends string>(router: Router<R, E, P>): Router<never, E, P> => {
|
|
143
|
-
const provided: Router<never, E, P> = {
|
|
144
|
-
...router,
|
|
145
|
-
params: pipe(router.params, Fx.provideContext(environment)),
|
|
146
|
-
route: Route.provideContext(environment)(router.route),
|
|
147
|
-
createPath: ((other, ...params) =>
|
|
148
|
-
Effect.provideContext<R>(environment)(router.createPath(other, ...params))) as Router<
|
|
149
|
-
never,
|
|
150
|
-
P
|
|
151
|
-
>['createPath'],
|
|
152
|
-
provideContext: (env) => provideContext(env)(provided),
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return provided
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export interface Redirect {
|
|
160
|
-
readonly _tag: 'Redirect'
|
|
161
|
-
readonly path: string
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export namespace Redirect {
|
|
165
|
-
export const make = (path: string): Redirect => ({ _tag: 'Redirect', path })
|
|
166
|
-
|
|
167
|
-
export const is = (r: unknown): r is Redirect =>
|
|
168
|
-
typeof r === 'object' && r !== null && '_tag' in r && r._tag === 'Redirect'
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function redirect(path: string) {
|
|
172
|
-
return Effect.fail<Redirect>(Redirect.make(path))
|
|
36
|
+
readonly navigation: Navigation.Navigation
|
|
173
37
|
}
|
|
174
38
|
|
|
175
|
-
|
|
176
|
-
Fx.fail<Redirect>(Redirect.make(path))
|
|
177
|
-
|
|
178
|
-
export const redirectTo = <R, E, P extends string>(
|
|
179
|
-
route: Route.Route<R, E, P>,
|
|
180
|
-
...[params]: [keyof Path.ParamsOf<P>] extends [never] ? [{}?] : [Path.ParamsOf<P>]
|
|
181
|
-
): Effect.Effect<Router, Redirect, never> =>
|
|
182
|
-
pipe(
|
|
183
|
-
Router.withEffect((r) => r.createPath(route as any, params as any)),
|
|
184
|
-
Effect.flatMap(redirect),
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
redirectTo.fx = <R, E, P extends string>(
|
|
188
|
-
route: Route.Route<R, E, P>,
|
|
189
|
-
...params: [keyof Path.ParamsOf<P>] extends [never] ? [{}?] : [(path: string) => Path.ParamsOf<P>]
|
|
190
|
-
): Fx.Fx<Router, Redirect, never> =>
|
|
191
|
-
pipe(
|
|
192
|
-
Router.withEffect((r) => r.createPath(route as any, params as any)),
|
|
193
|
-
Fx.fromEffect,
|
|
194
|
-
Fx.switchMap(redirect.fx),
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
// TOOD: Add support for reading <base> tag for default Router path.
|
|
39
|
+
export const Router = Tag<Router>('Router')
|
|
198
40
|
|
|
199
|
-
export const
|
|
200
|
-
currentPath?: Fx.RefSubject<never, string>,
|
|
201
|
-
): Effect.Effect<Location | History | Window | Document | Scope.Scope, never, Router> =>
|
|
41
|
+
export const navigation: Layer.Layer<Navigation.Navigation, never, Router> = Router.layer(
|
|
202
42
|
Effect.gen(function* ($) {
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
if (!currentPath) {
|
|
207
|
-
currentPath = Fx.RefSubject.unsafeMake(
|
|
208
|
-
Effect.sync(() => getCurrentPathFromLocation(location)),
|
|
209
|
-
)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Patch history events to emit an event when the path changes
|
|
213
|
-
const historyEvents = yield* $(patchHistory)
|
|
214
|
-
|
|
215
|
-
// Update the current path when events occur:
|
|
216
|
-
// - popstate
|
|
217
|
-
// - hashchange
|
|
218
|
-
// - history events
|
|
219
|
-
yield* $(
|
|
220
|
-
Fx.mergeAll(addWindowListener('popstate'), addWindowListener('hashchange'), historyEvents),
|
|
221
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
222
|
-
Fx.switchMapEffect(() => currentPath!.set(getCurrentPathFromLocation(location))),
|
|
223
|
-
Fx.drain,
|
|
224
|
-
Effect.forkScoped,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
// Listen to path changes and update the current history location, if necessary
|
|
228
|
-
yield* $(
|
|
229
|
-
pipe(
|
|
230
|
-
currentPath,
|
|
231
|
-
Fx.skipRepeats,
|
|
232
|
-
Fx.observe((path) =>
|
|
233
|
-
Effect.sync(() => {
|
|
234
|
-
if (path !== getCurrentPathFromLocation(location)) {
|
|
235
|
-
history.pushState({}, '', path)
|
|
236
|
-
}
|
|
237
|
-
}),
|
|
238
|
-
),
|
|
239
|
-
Effect.forkScoped,
|
|
240
|
-
),
|
|
43
|
+
const navigation = yield* $(Navigation.Navigation)
|
|
44
|
+
const currentPath = navigation.currentEntry.map((destination) =>
|
|
45
|
+
getCurrentPathFromUrl(destination.url),
|
|
241
46
|
)
|
|
242
47
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return location.pathname + location.search + location.hash
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export const getCurrentPath = Router.withEffect((r) => r.currentPath.get)
|
|
262
|
-
|
|
263
|
-
export const getBasePath = Router.with((r) => {
|
|
264
|
-
const routers: Router<any, any>[] = [r]
|
|
265
|
-
let current: Router<any, any> = r
|
|
266
|
-
|
|
267
|
-
while (Option.isSome(current.parent)) {
|
|
268
|
-
current = current.parent.value
|
|
269
|
-
routers.push(current)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return routers.reduceRight((acc, r) => Path.pathJoin(r.route.path, acc), '')
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
const patchHistory = Effect.gen(function* ($) {
|
|
276
|
-
const history = yield* $(History)
|
|
277
|
-
const historyEvents = Fx.makeSubject<never, void>()
|
|
278
|
-
const runtime = yield* $(Effect.runtime<never>())
|
|
279
|
-
const runFork = Runtime.runFork(runtime)
|
|
280
|
-
const cleanup = patchHistory_(history, () => runFork(historyEvents.event()))
|
|
281
|
-
|
|
282
|
-
// unpatch history upon finalization
|
|
283
|
-
yield* $(Effect.addFinalizer(() => Effect.sync(cleanup)))
|
|
284
|
-
|
|
285
|
-
return historyEvents
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
function patchHistory_(history: History, sendEvent: () => void) {
|
|
289
|
-
const pushState = history.pushState.bind(history)
|
|
290
|
-
const replaceState = history.replaceState.bind(history)
|
|
291
|
-
const go = history.go.bind(history)
|
|
292
|
-
const back = history.back.bind(history)
|
|
293
|
-
const forward = history.forward.bind(history)
|
|
294
|
-
|
|
295
|
-
history.pushState = function (state, title, url) {
|
|
296
|
-
pushState(state, title, url)
|
|
297
|
-
sendEvent()
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
history.replaceState = function (state, title, url) {
|
|
301
|
-
replaceState(state, title, url)
|
|
302
|
-
sendEvent()
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
history.go = function (delta) {
|
|
306
|
-
go(delta)
|
|
307
|
-
sendEvent()
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
history.back = function () {
|
|
311
|
-
back()
|
|
312
|
-
sendEvent()
|
|
313
|
-
}
|
|
48
|
+
function makeRouter<P extends string>(
|
|
49
|
+
route: Route<P>,
|
|
50
|
+
parent: Option.Option<Router<any>>,
|
|
51
|
+
): Router<P> {
|
|
52
|
+
const router: Router<P> = {
|
|
53
|
+
route,
|
|
54
|
+
navigation,
|
|
55
|
+
params: currentPath.filterMap(route.match),
|
|
56
|
+
define: <P2 extends string>(other: Route<P2>): Router<PathJoin<[P, P2]>> =>
|
|
57
|
+
makeRouter(route.concat(other), Option.some(router)),
|
|
58
|
+
parent,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return router
|
|
62
|
+
}
|
|
314
63
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
64
|
+
return makeRouter(Route(navigation.base), Option.none())
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
319
67
|
|
|
320
|
-
|
|
321
|
-
return
|
|
322
|
-
history.pushState = pushState
|
|
323
|
-
history.replaceState = replaceState
|
|
324
|
-
history.go = go
|
|
325
|
-
history.back = back
|
|
326
|
-
history.forward = forward
|
|
327
|
-
}
|
|
68
|
+
export function getCurrentPathFromUrl(url: URL): string {
|
|
69
|
+
return url.pathname + url.search + url.hash
|
|
328
70
|
}
|
|
329
71
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
72
|
+
export const dom = (
|
|
73
|
+
options?: Navigation.DomNavigationOptions,
|
|
74
|
+
): Layer.Layer<GlobalThis | Window, never, DomServices | Navigation.Navigation | Router> =>
|
|
75
|
+
Layer.provideMerge(
|
|
76
|
+
localStorage,
|
|
77
|
+
Layer.provideMerge(domServices, Layer.provideMerge(Navigation.dom(options), navigation)),
|
|
78
|
+
)
|
|
333
79
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
80
|
+
export const memory = (
|
|
81
|
+
options: Navigation.MemoryNavigationOptions,
|
|
82
|
+
): Layer.Layer<never, never, Navigation.Navigation | Router> =>
|
|
83
|
+
Layer.provideMerge(Navigation.memory(options), navigation)
|