@tanstack/react-router 0.0.1-beta.204 → 0.0.1-beta.206
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/build/cjs/RouterProvider.js +963 -0
- package/build/cjs/RouterProvider.js.map +1 -0
- package/build/cjs/fileRoute.js +29 -0
- package/build/cjs/fileRoute.js.map +1 -0
- package/build/cjs/index.js +69 -21
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/path.js +211 -0
- package/build/cjs/path.js.map +1 -0
- package/build/cjs/qss.js +65 -0
- package/build/cjs/qss.js.map +1 -0
- package/build/cjs/react.js +148 -190
- package/build/cjs/react.js.map +1 -1
- package/build/cjs/redirects.js +27 -0
- package/build/cjs/redirects.js.map +1 -0
- package/build/cjs/route.js +136 -0
- package/build/cjs/route.js.map +1 -0
- package/build/cjs/router.js +203 -0
- package/build/cjs/router.js.map +1 -0
- package/build/cjs/searchParams.js +83 -0
- package/build/cjs/searchParams.js.map +1 -0
- package/build/cjs/utils.js +196 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +1801 -211
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +385 -164
- package/build/types/RouteMatch.d.ts +23 -0
- package/build/types/RouterProvider.d.ts +54 -0
- package/build/types/awaited.d.ts +0 -8
- package/build/types/defer.d.ts +0 -0
- package/build/types/fileRoute.d.ts +17 -0
- package/build/types/history.d.ts +7 -0
- package/build/types/index.d.ts +17 -4
- package/build/types/link.d.ts +98 -0
- package/build/types/location.d.ts +14 -0
- package/build/types/path.d.ts +16 -0
- package/build/types/qss.d.ts +2 -0
- package/build/types/react.d.ts +23 -83
- package/build/types/redirects.d.ts +10 -0
- package/build/types/route.d.ts +222 -0
- package/build/types/routeInfo.d.ts +22 -0
- package/build/types/router.d.ts +115 -0
- package/build/types/scroll-restoration.d.ts +0 -3
- package/build/types/searchParams.d.ts +7 -0
- package/build/types/utils.d.ts +48 -0
- package/build/umd/index.development.js +1118 -1540
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +2 -33
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -4
- package/src/RouteMatch.ts +28 -0
- package/src/RouterProvider.tsx +1390 -0
- package/src/awaited.tsx +40 -40
- package/src/defer.ts +55 -0
- package/src/fileRoute.ts +143 -0
- package/src/history.ts +8 -0
- package/src/index.tsx +18 -5
- package/src/link.ts +347 -0
- package/src/location.ts +14 -0
- package/src/path.ts +256 -0
- package/src/qss.ts +53 -0
- package/src/react.tsx +174 -422
- package/src/redirects.ts +31 -0
- package/src/route.ts +710 -0
- package/src/routeInfo.ts +68 -0
- package/src/router.ts +373 -0
- package/src/scroll-restoration.tsx +205 -27
- package/src/searchParams.ts +78 -0
- package/src/utils.ts +257 -0
- package/build/cjs/awaited.js +0 -45
- package/build/cjs/awaited.js.map +0 -1
- package/build/cjs/scroll-restoration.js +0 -56
- package/build/cjs/scroll-restoration.js.map +0 -1
package/src/routeInfo.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AnyRoute, Route } from './route'
|
|
2
|
+
import { Expand, UnionToIntersection } from './utils'
|
|
3
|
+
|
|
4
|
+
export type ParseRoute<TRouteTree extends AnyRoute> =
|
|
5
|
+
| TRouteTree
|
|
6
|
+
| ParseRouteChildren<TRouteTree>
|
|
7
|
+
|
|
8
|
+
export type ParseRouteChildren<TRouteTree extends AnyRoute> =
|
|
9
|
+
TRouteTree extends Route<
|
|
10
|
+
any,
|
|
11
|
+
any,
|
|
12
|
+
any,
|
|
13
|
+
any,
|
|
14
|
+
any,
|
|
15
|
+
any,
|
|
16
|
+
any,
|
|
17
|
+
any,
|
|
18
|
+
any,
|
|
19
|
+
any,
|
|
20
|
+
any,
|
|
21
|
+
any,
|
|
22
|
+
infer TChildren,
|
|
23
|
+
any
|
|
24
|
+
>
|
|
25
|
+
? unknown extends TChildren
|
|
26
|
+
? never
|
|
27
|
+
: TChildren extends AnyRoute[]
|
|
28
|
+
? {
|
|
29
|
+
[TId in TChildren[number]['id'] as string]: ParseRoute<
|
|
30
|
+
TChildren[number]
|
|
31
|
+
>
|
|
32
|
+
}[string]
|
|
33
|
+
: never
|
|
34
|
+
: never
|
|
35
|
+
|
|
36
|
+
export type RoutesById<TRouteTree extends AnyRoute> = {
|
|
37
|
+
[K in ParseRoute<TRouteTree> as K['id']]: K
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type RouteById<TRouteTree extends AnyRoute, TId> = Extract<
|
|
41
|
+
ParseRoute<TRouteTree>,
|
|
42
|
+
{ id: TId }
|
|
43
|
+
>
|
|
44
|
+
|
|
45
|
+
export type RouteIds<TRouteTree extends AnyRoute> = ParseRoute<TRouteTree>['id']
|
|
46
|
+
|
|
47
|
+
export type RoutesByPath<TRouteTree extends AnyRoute> = {
|
|
48
|
+
[K in ParseRoute<TRouteTree> as K['fullPath']]: K
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type RouteByPath<TRouteTree extends AnyRoute, TPath> = Extract<
|
|
52
|
+
ParseRoute<TRouteTree>,
|
|
53
|
+
{ fullPath: TPath }
|
|
54
|
+
>
|
|
55
|
+
|
|
56
|
+
export type RoutePaths<TRouteTree extends AnyRoute> =
|
|
57
|
+
| ParseRoute<TRouteTree>['fullPath']
|
|
58
|
+
| '/'
|
|
59
|
+
|
|
60
|
+
export type FullSearchSchema<TRouteTree extends AnyRoute> = Partial<
|
|
61
|
+
Expand<
|
|
62
|
+
UnionToIntersection<ParseRoute<TRouteTree>['types']['fullSearchSchema']>
|
|
63
|
+
>
|
|
64
|
+
>
|
|
65
|
+
|
|
66
|
+
export type AllParams<TRouteTree extends AnyRoute> = Expand<
|
|
67
|
+
UnionToIntersection<ParseRoute<TRouteTree>['types']['allParams']>
|
|
68
|
+
>
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { RouterHistory } from '@tanstack/history'
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AnySearchSchema,
|
|
7
|
+
AnyRoute,
|
|
8
|
+
AnyContext,
|
|
9
|
+
AnyPathParams,
|
|
10
|
+
RouteMask,
|
|
11
|
+
} from './route'
|
|
12
|
+
import { FullSearchSchema } from './routeInfo'
|
|
13
|
+
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
14
|
+
import { PickAsRequired, Updater, NonNullableUpdater } from './utils'
|
|
15
|
+
import {
|
|
16
|
+
ErrorRouteComponent,
|
|
17
|
+
PendingRouteComponent,
|
|
18
|
+
RouteComponent,
|
|
19
|
+
} from './react'
|
|
20
|
+
import { RouteMatch } from './RouteMatch'
|
|
21
|
+
import { ParsedLocation } from './location'
|
|
22
|
+
import { LocationState } from './location'
|
|
23
|
+
import { SearchSerializer, SearchParser } from './searchParams'
|
|
24
|
+
|
|
25
|
+
//
|
|
26
|
+
|
|
27
|
+
declare global {
|
|
28
|
+
interface Window {
|
|
29
|
+
__TSR_DEHYDRATED__?: HydrationCtx
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Register {
|
|
34
|
+
// router: Router
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type AnyRouter = Router<any, any>
|
|
38
|
+
|
|
39
|
+
export type RegisteredRouter = Register extends {
|
|
40
|
+
router: infer TRouter extends AnyRouter
|
|
41
|
+
}
|
|
42
|
+
? TRouter
|
|
43
|
+
: AnyRouter
|
|
44
|
+
|
|
45
|
+
export type HydrationCtx = {
|
|
46
|
+
router: DehydratedRouter
|
|
47
|
+
payload: Record<string, any>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
51
|
+
AnyContext extends TRouteTree['types']['routerMeta']
|
|
52
|
+
? {
|
|
53
|
+
meta?: TRouteTree['types']['routerMeta']
|
|
54
|
+
}
|
|
55
|
+
: {
|
|
56
|
+
meta: TRouteTree['types']['routerMeta']
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RouterOptions<
|
|
60
|
+
TRouteTree extends AnyRoute,
|
|
61
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
62
|
+
> {
|
|
63
|
+
history?: RouterHistory
|
|
64
|
+
stringifySearch?: SearchSerializer
|
|
65
|
+
parseSearch?: SearchParser
|
|
66
|
+
defaultPreload?: false | 'intent'
|
|
67
|
+
defaultPreloadDelay?: number
|
|
68
|
+
defaultComponent?: RouteComponent<AnySearchSchema, AnyPathParams, AnyContext>
|
|
69
|
+
defaultErrorComponent?: ErrorRouteComponent<
|
|
70
|
+
AnySearchSchema,
|
|
71
|
+
AnyPathParams,
|
|
72
|
+
AnyContext
|
|
73
|
+
>
|
|
74
|
+
defaultPendingComponent?: PendingRouteComponent<
|
|
75
|
+
AnySearchSchema,
|
|
76
|
+
AnyPathParams,
|
|
77
|
+
AnyContext
|
|
78
|
+
>
|
|
79
|
+
defaultMaxAge?: number
|
|
80
|
+
defaultGcMaxAge?: number
|
|
81
|
+
defaultPreloadMaxAge?: number
|
|
82
|
+
caseSensitive?: boolean
|
|
83
|
+
routeTree?: TRouteTree
|
|
84
|
+
basepath?: string
|
|
85
|
+
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
|
|
86
|
+
meta?: TRouteTree['types']['routerMeta']
|
|
87
|
+
// dehydrate?: () => TDehydrated
|
|
88
|
+
// hydrate?: (dehydrated: TDehydrated) => void
|
|
89
|
+
routeMasks?: RouteMask<TRouteTree>[]
|
|
90
|
+
unmaskOnReload?: boolean
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
94
|
+
status: 'idle' | 'pending'
|
|
95
|
+
isFetching: boolean
|
|
96
|
+
matches: RouteMatch<TRouteTree>[]
|
|
97
|
+
pendingMatches: RouteMatch<TRouteTree>[]
|
|
98
|
+
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
99
|
+
resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
100
|
+
lastUpdated: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
|
|
104
|
+
|
|
105
|
+
export interface BuildNextOptions {
|
|
106
|
+
to?: string | number | null
|
|
107
|
+
params?: true | Updater<unknown>
|
|
108
|
+
search?: true | Updater<unknown>
|
|
109
|
+
hash?: true | Updater<string>
|
|
110
|
+
state?: true | NonNullableUpdater<LocationState>
|
|
111
|
+
mask?: {
|
|
112
|
+
to?: string | number | null
|
|
113
|
+
params?: true | Updater<unknown>
|
|
114
|
+
search?: true | Updater<unknown>
|
|
115
|
+
hash?: true | Updater<string>
|
|
116
|
+
state?: true | NonNullableUpdater<LocationState>
|
|
117
|
+
unmaskOnReload?: boolean
|
|
118
|
+
}
|
|
119
|
+
from?: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface DehydratedRouterState {
|
|
123
|
+
dehydratedMatches: DehydratedRouteMatch[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type DehydratedRouteMatch = Pick<
|
|
127
|
+
RouteMatch,
|
|
128
|
+
'fetchedAt' | 'invalid' | 'id' | 'status' | 'updatedAt'
|
|
129
|
+
>
|
|
130
|
+
|
|
131
|
+
export interface DehydratedRouter {
|
|
132
|
+
state: DehydratedRouterState
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type RouterConstructorOptions<
|
|
136
|
+
TRouteTree extends AnyRoute,
|
|
137
|
+
TDehydrated extends Record<string, any>,
|
|
138
|
+
> = Omit<RouterOptions<TRouteTree, TDehydrated>, 'context'> &
|
|
139
|
+
RouterContextOptions<TRouteTree>
|
|
140
|
+
|
|
141
|
+
export const componentTypes = [
|
|
142
|
+
'component',
|
|
143
|
+
'errorComponent',
|
|
144
|
+
'pendingComponent',
|
|
145
|
+
] as const
|
|
146
|
+
|
|
147
|
+
export type RouterEvents = {
|
|
148
|
+
onBeforeLoad: {
|
|
149
|
+
type: 'onBeforeLoad'
|
|
150
|
+
from: ParsedLocation
|
|
151
|
+
to: ParsedLocation
|
|
152
|
+
pathChanged: boolean
|
|
153
|
+
}
|
|
154
|
+
onLoad: {
|
|
155
|
+
type: 'onLoad'
|
|
156
|
+
from: ParsedLocation
|
|
157
|
+
to: ParsedLocation
|
|
158
|
+
pathChanged: boolean
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type RouterEvent = RouterEvents[keyof RouterEvents]
|
|
163
|
+
|
|
164
|
+
export type RouterListener<TRouterEvent extends RouterEvent> = {
|
|
165
|
+
eventType: TRouterEvent['type']
|
|
166
|
+
fn: ListenerFn<TRouterEvent>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export class Router<
|
|
170
|
+
TRouteTree extends AnyRoute = AnyRoute,
|
|
171
|
+
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
172
|
+
> {
|
|
173
|
+
options: PickAsRequired<
|
|
174
|
+
RouterOptions<TRouteTree, TDehydrated>,
|
|
175
|
+
'stringifySearch' | 'parseSearch' | 'meta'
|
|
176
|
+
>
|
|
177
|
+
routeTree: TRouteTree
|
|
178
|
+
// dehydratedData?: TDehydrated
|
|
179
|
+
// resetNextScroll = false
|
|
180
|
+
// tempLocationKey = `${Math.round(Math.random() * 10000000)}`
|
|
181
|
+
|
|
182
|
+
constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
|
|
183
|
+
this.options = {
|
|
184
|
+
defaultPreloadDelay: 50,
|
|
185
|
+
meta: undefined!,
|
|
186
|
+
...options,
|
|
187
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
188
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.routeTree = this.options.routeTree as TRouteTree
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
195
|
+
|
|
196
|
+
subscribe = <TType extends keyof RouterEvents>(
|
|
197
|
+
eventType: TType,
|
|
198
|
+
fn: ListenerFn<RouterEvents[TType]>,
|
|
199
|
+
) => {
|
|
200
|
+
const listener: RouterListener<any> = {
|
|
201
|
+
eventType,
|
|
202
|
+
fn,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.subscribers.add(listener)
|
|
206
|
+
|
|
207
|
+
return () => {
|
|
208
|
+
this.subscribers.delete(listener)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
emit = (routerEvent: RouterEvent) => {
|
|
213
|
+
this.subscribers.forEach((listener) => {
|
|
214
|
+
if (listener.eventType === routerEvent.type) {
|
|
215
|
+
listener.fn(routerEvent)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// dehydrate = (): DehydratedRouter => {
|
|
221
|
+
// return {
|
|
222
|
+
// state: {
|
|
223
|
+
// dehydratedMatches: state.matches.map((d) =>
|
|
224
|
+
// pick(d, ['fetchedAt', 'invalid', 'id', 'status', 'updatedAt']),
|
|
225
|
+
// ),
|
|
226
|
+
// },
|
|
227
|
+
// }
|
|
228
|
+
// }
|
|
229
|
+
|
|
230
|
+
// hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
|
|
231
|
+
// let _ctx = __do_not_use_server_ctx
|
|
232
|
+
// // Client hydrates from window
|
|
233
|
+
// if (typeof document !== 'undefined') {
|
|
234
|
+
// _ctx = window.__TSR_DEHYDRATED__
|
|
235
|
+
// }
|
|
236
|
+
|
|
237
|
+
// invariant(
|
|
238
|
+
// _ctx,
|
|
239
|
+
// 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
|
|
240
|
+
// )
|
|
241
|
+
|
|
242
|
+
// const ctx = _ctx
|
|
243
|
+
// this.dehydratedData = ctx.payload as any
|
|
244
|
+
// this.options.hydrate?.(ctx.payload as any)
|
|
245
|
+
// const dehydratedState = ctx.router.state
|
|
246
|
+
|
|
247
|
+
// let matches = this.matchRoutes(
|
|
248
|
+
// state.location.pathname,
|
|
249
|
+
// state.location.search,
|
|
250
|
+
// ).map((match) => {
|
|
251
|
+
// const dehydratedMatch = dehydratedState.dehydratedMatches.find(
|
|
252
|
+
// (d) => d.id === match.id,
|
|
253
|
+
// )
|
|
254
|
+
|
|
255
|
+
// invariant(
|
|
256
|
+
// dehydratedMatch,
|
|
257
|
+
// `Could not find a client-side match for dehydrated match with id: ${match.id}!`,
|
|
258
|
+
// )
|
|
259
|
+
|
|
260
|
+
// if (dehydratedMatch) {
|
|
261
|
+
// return {
|
|
262
|
+
// ...match,
|
|
263
|
+
// ...dehydratedMatch,
|
|
264
|
+
// }
|
|
265
|
+
// }
|
|
266
|
+
// return match
|
|
267
|
+
// })
|
|
268
|
+
|
|
269
|
+
// this.setState((s) => {
|
|
270
|
+
// return {
|
|
271
|
+
// ...s,
|
|
272
|
+
// matches: dehydratedState.dehydratedMatches as any,
|
|
273
|
+
// }
|
|
274
|
+
// })
|
|
275
|
+
// }
|
|
276
|
+
|
|
277
|
+
// TODO:
|
|
278
|
+
// injectedHtml: (string | (() => Promise<string> | string))[] = []
|
|
279
|
+
|
|
280
|
+
// TODO:
|
|
281
|
+
// injectHtml = async (html: string | (() => Promise<string> | string)) => {
|
|
282
|
+
// this.injectedHtml.push(html)
|
|
283
|
+
// }
|
|
284
|
+
|
|
285
|
+
// TODO:
|
|
286
|
+
// dehydrateData = <T>(key: any, getData: T | (() => Promise<T> | T)) => {
|
|
287
|
+
// if (typeof document === 'undefined') {
|
|
288
|
+
// const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
289
|
+
|
|
290
|
+
// this.injectHtml(async () => {
|
|
291
|
+
// const id = `__TSR_DEHYDRATED__${strKey}`
|
|
292
|
+
// const data =
|
|
293
|
+
// typeof getData === 'function' ? await (getData as any)() : getData
|
|
294
|
+
// return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
|
|
295
|
+
// strKey,
|
|
296
|
+
// )}"] = ${JSON.stringify(data)}
|
|
297
|
+
// ;(() => {
|
|
298
|
+
// var el = document.getElementById('${id}')
|
|
299
|
+
// el.parentElement.removeChild(el)
|
|
300
|
+
// })()
|
|
301
|
+
// </script>`
|
|
302
|
+
// })
|
|
303
|
+
|
|
304
|
+
// return () => this.hydrateData<T>(key)
|
|
305
|
+
// }
|
|
306
|
+
|
|
307
|
+
// return () => undefined
|
|
308
|
+
// }
|
|
309
|
+
|
|
310
|
+
// hydrateData = <T = unknown>(key: any) => {
|
|
311
|
+
// if (typeof document !== 'undefined') {
|
|
312
|
+
// const strKey = typeof key === 'string' ? key : JSON.stringify(key)
|
|
313
|
+
|
|
314
|
+
// return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
|
|
315
|
+
// }
|
|
316
|
+
|
|
317
|
+
// return undefined
|
|
318
|
+
// }
|
|
319
|
+
|
|
320
|
+
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
321
|
+
// state.matches
|
|
322
|
+
// .find((d) => d.id === matchId)
|
|
323
|
+
// ?.__promisesByKey[key]?.resolve(value)
|
|
324
|
+
// }
|
|
325
|
+
|
|
326
|
+
// setRouteMatch = (
|
|
327
|
+
// id: string,
|
|
328
|
+
// pending: boolean,
|
|
329
|
+
// updater: NonNullableUpdater<RouteMatch<TRouteTree>>,
|
|
330
|
+
// ) => {
|
|
331
|
+
// const key = pending ? 'pendingMatches' : 'matches'
|
|
332
|
+
|
|
333
|
+
// this.setState((prev) => {
|
|
334
|
+
// return {
|
|
335
|
+
// ...prev,
|
|
336
|
+
// [key]: prev[key].map((d) => {
|
|
337
|
+
// if (d.id === id) {
|
|
338
|
+
// return functionalUpdate(updater, d)
|
|
339
|
+
// }
|
|
340
|
+
|
|
341
|
+
// return d
|
|
342
|
+
// }),
|
|
343
|
+
// }
|
|
344
|
+
// })
|
|
345
|
+
// }
|
|
346
|
+
|
|
347
|
+
// setPendingRouteMatch = (
|
|
348
|
+
// id: string,
|
|
349
|
+
// updater: NonNullableUpdater<RouteMatch<TRouteTree>>,
|
|
350
|
+
// ) => {
|
|
351
|
+
// this.setRouteMatch(id, true, updater)
|
|
352
|
+
// }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function escapeJSON(jsonString: string) {
|
|
356
|
+
return jsonString
|
|
357
|
+
.replace(/\\/g, '\\\\') // Escape backslashes
|
|
358
|
+
.replace(/'/g, "\\'") // Escape single quotes
|
|
359
|
+
.replace(/"/g, '\\"') // Escape double quotes
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
363
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
364
|
+
// information along the way
|
|
365
|
+
export function lazyFn<
|
|
366
|
+
T extends Record<string, (...args: any[]) => any>,
|
|
367
|
+
TKey extends keyof T = 'default',
|
|
368
|
+
>(fn: () => Promise<T>, key?: TKey) {
|
|
369
|
+
return async (...args: Parameters<T[TKey]>): Promise<ReturnType<T[TKey]>> => {
|
|
370
|
+
const imported = await fn()
|
|
371
|
+
return imported[key || 'default'](...args)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -1,27 +1,205 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
// import * as React from 'react'
|
|
2
|
+
// import { useRouter } from './react'
|
|
3
|
+
|
|
4
|
+
// const useLayoutEffect =
|
|
5
|
+
// typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect
|
|
6
|
+
|
|
7
|
+
// import { AnyRouter, RouterState } from './router'
|
|
8
|
+
// import { ParsedLocation } from './location'
|
|
9
|
+
|
|
10
|
+
// const windowKey = 'window'
|
|
11
|
+
// const delimiter = '___'
|
|
12
|
+
|
|
13
|
+
// let weakScrolledElementsByRestoreKey: Record<string, WeakSet<any>> = {}
|
|
14
|
+
|
|
15
|
+
// type CacheValue = Record<string, { scrollX: number; scrollY: number }>
|
|
16
|
+
|
|
17
|
+
// type Cache = {
|
|
18
|
+
// current: CacheValue
|
|
19
|
+
// set: (key: string, value: any) => void
|
|
20
|
+
// }
|
|
21
|
+
|
|
22
|
+
// let cache: Cache
|
|
23
|
+
|
|
24
|
+
// let pathDidChange = false
|
|
25
|
+
|
|
26
|
+
// const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage
|
|
27
|
+
|
|
28
|
+
// export type ScrollRestorationOptions = {
|
|
29
|
+
// getKey?: (location: ParsedLocation) => string
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
// const defaultGetKey = (location: ParsedLocation) => location.state.key!
|
|
33
|
+
|
|
34
|
+
// export function watchScrollPositions(
|
|
35
|
+
// router: AnyRouter,
|
|
36
|
+
// state: RouterState,
|
|
37
|
+
// opts?: ScrollRestorationOptions,
|
|
38
|
+
// ) {
|
|
39
|
+
// const getKey = opts?.getKey || defaultGetKey
|
|
40
|
+
|
|
41
|
+
// if (sessionsStorage) {
|
|
42
|
+
// if (!cache) {
|
|
43
|
+
// cache = (() => {
|
|
44
|
+
// const storageKey = 'tsr-scroll-restoration-v1'
|
|
45
|
+
|
|
46
|
+
// const current: CacheValue = JSON.parse(
|
|
47
|
+
// window.sessionStorage.getItem(storageKey) || '{}',
|
|
48
|
+
// )
|
|
49
|
+
|
|
50
|
+
// return {
|
|
51
|
+
// current,
|
|
52
|
+
// set: (key: string, value: any) => {
|
|
53
|
+
// current[key] = value
|
|
54
|
+
// window.sessionStorage.setItem(storageKey, JSON.stringify(cache))
|
|
55
|
+
// },
|
|
56
|
+
// }
|
|
57
|
+
// })()
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
|
|
61
|
+
// const { history } = window
|
|
62
|
+
// if (history.scrollRestoration) {
|
|
63
|
+
// history.scrollRestoration = 'manual'
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
// const onScroll = (event: Event) => {
|
|
67
|
+
// const restoreKey = getKey(state.resolvedLocation)
|
|
68
|
+
|
|
69
|
+
// if (!weakScrolledElementsByRestoreKey[restoreKey]) {
|
|
70
|
+
// weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet()
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// const set = weakScrolledElementsByRestoreKey[restoreKey]!
|
|
74
|
+
|
|
75
|
+
// if (set.has(event.target)) return
|
|
76
|
+
// set.add(event.target)
|
|
77
|
+
|
|
78
|
+
// const cacheKey = [
|
|
79
|
+
// restoreKey,
|
|
80
|
+
// event.target === document || event.target === window
|
|
81
|
+
// ? windowKey
|
|
82
|
+
// : getCssSelector(event.target),
|
|
83
|
+
// ].join(delimiter)
|
|
84
|
+
|
|
85
|
+
// if (!cache.current[cacheKey]) {
|
|
86
|
+
// cache.set(cacheKey, {
|
|
87
|
+
// scrollX: NaN,
|
|
88
|
+
// scrollY: NaN,
|
|
89
|
+
// })
|
|
90
|
+
// }
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
// const getCssSelector = (el: any): string => {
|
|
94
|
+
// let path = [],
|
|
95
|
+
// parent
|
|
96
|
+
// while ((parent = el.parentNode)) {
|
|
97
|
+
// path.unshift(
|
|
98
|
+
// `${el.tagName}:nth-child(${
|
|
99
|
+
// ([].indexOf as any).call(parent.children, el) + 1
|
|
100
|
+
// })`,
|
|
101
|
+
// )
|
|
102
|
+
// el = parent
|
|
103
|
+
// }
|
|
104
|
+
// return `${path.join(' > ')}`.toLowerCase()
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
// const onPathWillChange = (from: ParsedLocation) => {
|
|
108
|
+
// const restoreKey = getKey(from)
|
|
109
|
+
// for (const cacheKey in cache.current) {
|
|
110
|
+
// const entry = cache.current[cacheKey]!
|
|
111
|
+
// const [key, elementSelector] = cacheKey.split(delimiter)
|
|
112
|
+
// if (restoreKey === key) {
|
|
113
|
+
// if (elementSelector === windowKey) {
|
|
114
|
+
// entry.scrollX = window.scrollX || 0
|
|
115
|
+
// entry.scrollY = window.scrollY || 0
|
|
116
|
+
// } else if (elementSelector) {
|
|
117
|
+
// const element = document.querySelector(elementSelector)
|
|
118
|
+
// entry.scrollX = element?.scrollLeft || 0
|
|
119
|
+
// entry.scrollY = element?.scrollTop || 0
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
// cache.set(cacheKey, entry)
|
|
123
|
+
// }
|
|
124
|
+
// }
|
|
125
|
+
// }
|
|
126
|
+
|
|
127
|
+
// const onPathChange = () => {
|
|
128
|
+
// pathDidChange = true
|
|
129
|
+
// }
|
|
130
|
+
|
|
131
|
+
// if (typeof document !== 'undefined') {
|
|
132
|
+
// document.addEventListener('scroll', onScroll, true)
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
// const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', (event) => {
|
|
136
|
+
// if (event.pathChanged) onPathWillChange(event.from)
|
|
137
|
+
// })
|
|
138
|
+
|
|
139
|
+
// const unsubOnLoad = router.subscribe('onLoad', (event) => {
|
|
140
|
+
// if (event.pathChanged) onPathChange()
|
|
141
|
+
// })
|
|
142
|
+
|
|
143
|
+
// return () => {
|
|
144
|
+
// document.removeEventListener('scroll', onScroll)
|
|
145
|
+
// unsubOnBeforeLoad()
|
|
146
|
+
// unsubOnLoad()
|
|
147
|
+
// }
|
|
148
|
+
// }
|
|
149
|
+
|
|
150
|
+
// export function restoreScrollPositions(
|
|
151
|
+
// router: AnyRouter,
|
|
152
|
+
// state: RouterState,
|
|
153
|
+
// opts?: ScrollRestorationOptions,
|
|
154
|
+
// ) {
|
|
155
|
+
// if (pathDidChange) {
|
|
156
|
+
// if (!router.resetNextScroll) {
|
|
157
|
+
// return
|
|
158
|
+
// }
|
|
159
|
+
|
|
160
|
+
// const getKey = opts?.getKey || defaultGetKey
|
|
161
|
+
|
|
162
|
+
// pathDidChange = false
|
|
163
|
+
|
|
164
|
+
// const restoreKey = getKey(state.location)
|
|
165
|
+
// let windowRestored = false
|
|
166
|
+
|
|
167
|
+
// for (const cacheKey in cache.current) {
|
|
168
|
+
// const entry = cache.current[cacheKey]!
|
|
169
|
+
// const [key, elementSelector] = cacheKey.split(delimiter)
|
|
170
|
+
// if (key === restoreKey) {
|
|
171
|
+
// if (elementSelector === windowKey) {
|
|
172
|
+
// windowRestored = true
|
|
173
|
+
// window.scrollTo(entry.scrollX, entry.scrollY)
|
|
174
|
+
// } else if (elementSelector) {
|
|
175
|
+
// const element = document.querySelector(elementSelector)
|
|
176
|
+
// if (element) {
|
|
177
|
+
// element.scrollLeft = entry.scrollX
|
|
178
|
+
// element.scrollTop = entry.scrollY
|
|
179
|
+
// }
|
|
180
|
+
// }
|
|
181
|
+
// }
|
|
182
|
+
// }
|
|
183
|
+
|
|
184
|
+
// if (!windowRestored) {
|
|
185
|
+
// window.scrollTo(0, 0)
|
|
186
|
+
// }
|
|
187
|
+
// }
|
|
188
|
+
// }
|
|
189
|
+
|
|
190
|
+
// export function useScrollRestoration(options?: ScrollRestorationOptions) {
|
|
191
|
+
// const { router, state } = useRouter()
|
|
192
|
+
|
|
193
|
+
// useLayoutEffect(() => {
|
|
194
|
+
// return watchScrollPositions(router, state, options)
|
|
195
|
+
// }, [])
|
|
196
|
+
|
|
197
|
+
// useLayoutEffect(() => {
|
|
198
|
+
// restoreScrollPositions(router, state, options)
|
|
199
|
+
// })
|
|
200
|
+
// }
|
|
201
|
+
|
|
202
|
+
// export function ScrollRestoration(props: ScrollRestorationOptions) {
|
|
203
|
+
// useScrollRestoration(props)
|
|
204
|
+
// return null
|
|
205
|
+
// }
|