crelte 0.2.2 → 0.3.1
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/Crelte.d.ts +61 -9
- package/dist/Crelte.d.ts.map +1 -1
- package/dist/Crelte.js +42 -10
- package/dist/CrelteRequest.d.ts +3 -11
- package/dist/CrelteRequest.d.ts.map +1 -1
- package/dist/CrelteRequest.js +9 -19
- package/dist/graphql/GraphQl.d.ts +7 -0
- package/dist/graphql/GraphQl.d.ts.map +1 -1
- package/dist/graphql/GraphQl.js +16 -3
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/init/client.d.ts +0 -19
- package/dist/init/client.d.ts.map +1 -1
- package/dist/init/client.js +9 -12
- package/dist/init/server.d.ts +0 -4
- package/dist/init/server.d.ts.map +1 -1
- package/dist/init/server.js +2 -5
- package/dist/init/shared.d.ts.map +1 -1
- package/dist/init/shared.js +8 -8
- package/dist/loadData/Globals.d.ts +15 -31
- package/dist/loadData/Globals.d.ts.map +1 -1
- package/dist/loadData/Globals.js +65 -72
- package/dist/routing/InnerRouter.d.ts +1 -10
- package/dist/routing/InnerRouter.d.ts.map +1 -1
- package/dist/routing/InnerRouter.js +28 -23
- package/dist/routing/Request.d.ts +2 -0
- package/dist/routing/Request.d.ts.map +1 -1
- package/dist/routing/Request.js +9 -0
- package/dist/routing/Route.d.ts +56 -1
- package/dist/routing/Route.d.ts.map +1 -1
- package/dist/routing/Route.js +85 -2
- package/dist/routing/Router.d.ts +29 -4
- package/dist/routing/Router.d.ts.map +1 -1
- package/dist/routing/Router.js +39 -12
- package/dist/ssr/SsrCache.d.ts.map +1 -1
- package/dist/ssr/SsrCache.js +6 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +8 -0
- package/package.json +2 -2
- package/src/Crelte.ts +95 -13
- package/src/CrelteRequest.ts +14 -27
- package/src/graphql/GraphQl.ts +25 -6
- package/src/index.ts +19 -8
- package/src/init/client.ts +9 -40
- package/src/init/server.ts +2 -13
- package/src/init/shared.ts +8 -9
- package/src/loadData/Globals.ts +76 -93
- package/src/routing/InnerRouter.ts +38 -32
- package/src/routing/Request.ts +11 -0
- package/src/routing/Route.ts +93 -2
- package/src/routing/Router.ts +51 -20
- package/src/ssr/SsrCache.ts +6 -1
- package/src/utils.ts +10 -0
package/src/loadData/Globals.ts
CHANGED
|
@@ -4,25 +4,30 @@ const emergency = getGlobal('emergency');
|
|
|
4
4
|
|
|
5
5
|
// returns the data based on the current site (no store)
|
|
6
6
|
cr.getGlobal('emergency')
|
|
7
|
+
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { Writable } from 'crelte-std/stores';
|
|
10
11
|
|
|
11
|
-
export type GlobalWaiters = [(g:
|
|
12
|
+
export type GlobalWaiters<T> = [(g: T | null) => void];
|
|
12
13
|
|
|
13
14
|
export default class Globals {
|
|
14
15
|
// while the globals are not loaded if somebody calls
|
|
15
|
-
//
|
|
16
|
-
private waiters: Map<string, GlobalWaiters
|
|
17
|
-
private
|
|
18
|
-
private
|
|
19
|
-
private
|
|
16
|
+
// getAsync then we need to store the waiters
|
|
17
|
+
private waiters: Map<number, Map<string, GlobalWaiters<any>>>;
|
|
18
|
+
private data: Map<number, Map<string, any>>;
|
|
19
|
+
private stores: Map<string, Global<any>>;
|
|
20
|
+
private currentSiteId: number | null;
|
|
20
21
|
|
|
21
22
|
constructor() {
|
|
22
23
|
this.waiters = new Map();
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
24
|
+
this.data = new Map();
|
|
25
|
+
this.stores = new Map();
|
|
26
|
+
this.currentSiteId = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get<T = any>(name: string, siteId: number): T | null {
|
|
30
|
+
return this.data.get(siteId)?.get(name) ?? null;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -31,10 +36,10 @@ export default class Globals {
|
|
|
31
36
|
* ## Note
|
|
32
37
|
* This only works in loadData, in loadGlobalData this will
|
|
33
38
|
* always return null. In that context you should use
|
|
34
|
-
* `.
|
|
39
|
+
* `.getAsync`
|
|
35
40
|
*/
|
|
36
|
-
|
|
37
|
-
return this.
|
|
41
|
+
getStore<T = any>(name: string): Global<T> | null {
|
|
42
|
+
return this.stores.get(name) ?? null;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
@@ -44,15 +49,20 @@ export default class Globals {
|
|
|
44
49
|
* This is only useful in loadGlobalData in all other cases
|
|
45
50
|
* you can use `.getGlobal` which does return a Promise
|
|
46
51
|
*/
|
|
47
|
-
getAsync<T
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
getAsync<T = any>(name: string, siteId: number): Promise<T | null> {
|
|
53
|
+
if (this._wasLoaded(siteId))
|
|
54
|
+
return Promise.resolve(this.get(name, siteId));
|
|
55
|
+
|
|
56
|
+
let listeners = this.waiters.get(siteId);
|
|
57
|
+
if (!listeners) {
|
|
58
|
+
listeners = new Map();
|
|
59
|
+
this.waiters.set(siteId, listeners);
|
|
60
|
+
}
|
|
51
61
|
|
|
52
|
-
let waiter =
|
|
62
|
+
let waiter = listeners.get(name);
|
|
53
63
|
if (!waiter) {
|
|
54
64
|
waiter = [] as any;
|
|
55
|
-
|
|
65
|
+
listeners.set(name, waiter!);
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
return new Promise(resolve => {
|
|
@@ -61,88 +71,73 @@ export default class Globals {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
/** @hidden */
|
|
64
|
-
_wasLoaded(): boolean {
|
|
65
|
-
return this.
|
|
74
|
+
_wasLoaded(siteId: number): boolean {
|
|
75
|
+
return this.data.has(siteId);
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
// data is the data from the global graphql
|
|
69
79
|
// so it contains some keys and data which should be parsed
|
|
70
80
|
// and created a store for each key
|
|
81
|
+
// do not call this if _wasLoaded returns true with the same siteId
|
|
71
82
|
/** @hidden */
|
|
72
83
|
_setData(siteId: number, data: any) {
|
|
73
|
-
const
|
|
74
|
-
this.
|
|
84
|
+
const map = new Map(Object.entries(data));
|
|
85
|
+
this.data.set(siteId, map);
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!wasLoaded) {
|
|
81
|
-
this.waiters.forEach((waiters, key) => {
|
|
82
|
-
waiters.forEach(waiter => waiter(this.get(key)));
|
|
83
|
-
});
|
|
84
|
-
this.waiters.clear();
|
|
85
|
-
}
|
|
87
|
+
this.waiters.get(siteId)?.forEach((waiters, key) => {
|
|
88
|
+
waiters.forEach(waiter => waiter(map.get(key)));
|
|
89
|
+
});
|
|
90
|
+
this.waiters.delete(siteId);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
/** @hidden */
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
_updateSiteId(siteId: number) {
|
|
95
|
+
if (this.currentSiteId === siteId) return;
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
map.set(key, global.bySiteId(siteId));
|
|
94
|
-
}
|
|
97
|
+
const data = this.data.get(siteId) ?? new Map();
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// we set all global data to null via setSilent
|
|
100
|
+
// then set them all with the new data
|
|
101
|
+
// and update all of them
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
_updateSiteId(siteId: number) {
|
|
101
|
-
// todo we should only trigger
|
|
102
|
-
if (this.prevSiteId === siteId) return;
|
|
103
|
+
this.stores.forEach(global => global._setSilent(null));
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
data.forEach((value, key) => {
|
|
106
|
+
let global = this.stores.get(key);
|
|
107
|
+
if (global) {
|
|
108
|
+
global._setSilent(value);
|
|
109
|
+
} else {
|
|
110
|
+
global = new Global(key, value);
|
|
111
|
+
this.stores.set(key, global);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*
|
|
111
|
-
* Each global query should contain the siteId
|
|
112
|
-
*/
|
|
113
|
-
export interface GlobalData {
|
|
114
|
-
siteId?: number;
|
|
115
|
-
[key: string]: any;
|
|
115
|
+
this.stores.forEach(global => global._notify());
|
|
116
|
+
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
/**
|
|
119
120
|
* A globalSet store
|
|
120
121
|
*/
|
|
121
|
-
export class Global<T
|
|
122
|
+
export class Global<T = any> {
|
|
123
|
+
/** @hidden */
|
|
122
124
|
private inner: Writable<T>;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (!inner?.siteId) {
|
|
137
|
-
throw new Error(
|
|
138
|
-
`The global query ${name} does not contain the required siteId property`,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
inner = data;
|
|
125
|
+
|
|
126
|
+
constructor(name: string, data: T) {
|
|
127
|
+
// todo remove in v1.0
|
|
128
|
+
// In v0.2, we queried the global data for all sites.
|
|
129
|
+
// We now check if the siteId is present and notify the user to remove it.
|
|
130
|
+
if (
|
|
131
|
+
typeof (data as any).siteId === 'number' ||
|
|
132
|
+
(Array.isArray(data) && typeof data[0]?.siteId === 'number')
|
|
133
|
+
) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`The global query ${name} should not include the siteId` +
|
|
136
|
+
` property. Instead, use the siteId as a parameter.`,
|
|
137
|
+
);
|
|
143
138
|
}
|
|
144
139
|
|
|
145
|
-
this.inner = new Writable(
|
|
140
|
+
this.inner = new Writable(data);
|
|
146
141
|
}
|
|
147
142
|
|
|
148
143
|
/**
|
|
@@ -162,25 +157,13 @@ export class Global<T extends GlobalData> {
|
|
|
162
157
|
return this.inner.get();
|
|
163
158
|
}
|
|
164
159
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* ## Note
|
|
169
|
-
* If you pass a siteId which comes from craft
|
|
170
|
-
* you will never receive null
|
|
171
|
-
*/
|
|
172
|
-
bySiteId(siteId: number): T | null {
|
|
173
|
-
if (this.languages)
|
|
174
|
-
return this.languages.find(d => d.siteId === siteId) ?? null;
|
|
175
|
-
|
|
176
|
-
return this.inner.get();
|
|
160
|
+
/** @hidden */
|
|
161
|
+
_setSilent(value: T) {
|
|
162
|
+
this.inner.setSilent(value);
|
|
177
163
|
}
|
|
178
164
|
|
|
179
165
|
/** @hidden */
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const inner = this.languages.find(d => d.siteId === siteId);
|
|
184
|
-
this.inner.set(inner!);
|
|
166
|
+
_notify() {
|
|
167
|
+
this.inner.notify();
|
|
185
168
|
}
|
|
186
169
|
}
|
|
@@ -68,7 +68,10 @@ export default class InnerRouter {
|
|
|
68
68
|
req.origin = 'init';
|
|
69
69
|
window.history.scrollRestoration = 'manual';
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// we set it now instead of waiting for the onRoute call
|
|
72
|
+
// because the window.history is already set
|
|
73
|
+
this.route = req.toRoute();
|
|
74
|
+
this.onRoute(req, () => {});
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
/**
|
|
@@ -133,6 +136,7 @@ export default class InnerRouter {
|
|
|
133
136
|
return this.sites.find(s => s.id === id) ?? null;
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
// keep this doc in sync with Router.targetToRequest
|
|
136
140
|
/**
|
|
137
141
|
* Resolve a url or Route and convert it to a Request
|
|
138
142
|
*
|
|
@@ -150,6 +154,8 @@ export default class InnerRouter {
|
|
|
150
154
|
// exists
|
|
151
155
|
const site = this.route?.site ?? this.defaultSite();
|
|
152
156
|
target = new URL(site.uri + target, site.url);
|
|
157
|
+
} else if (!target) {
|
|
158
|
+
throw new Error('the url is not allowed to be empty');
|
|
153
159
|
} else {
|
|
154
160
|
target = new URL(target);
|
|
155
161
|
}
|
|
@@ -242,7 +248,11 @@ export default class InnerRouter {
|
|
|
242
248
|
if (currentMouseOver && link === currentMouseOver) return;
|
|
243
249
|
if (link && link.target.toLowerCase() === '_blank') return;
|
|
244
250
|
|
|
245
|
-
if (
|
|
251
|
+
if (
|
|
252
|
+
link &&
|
|
253
|
+
!link.hasAttribute('data-no-preload') &&
|
|
254
|
+
link.href
|
|
255
|
+
) {
|
|
246
256
|
this.preload(link.href);
|
|
247
257
|
}
|
|
248
258
|
|
|
@@ -288,13 +298,16 @@ export default class InnerRouter {
|
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
window.addEventListener('popstate', async e => {
|
|
291
|
-
if (!
|
|
301
|
+
if (!e.state?.route) return;
|
|
292
302
|
|
|
293
303
|
const req = this.targetToRequest(window.location.href);
|
|
294
304
|
req._fillFromState(e.state);
|
|
295
305
|
req.origin = 'pop';
|
|
296
306
|
|
|
297
|
-
|
|
307
|
+
// we set it now instead of waiting for the onRoute call
|
|
308
|
+
// because the window.history was already modified
|
|
309
|
+
this.route = req.toRoute();
|
|
310
|
+
this.onRoute(req, () => {});
|
|
298
311
|
});
|
|
299
312
|
}
|
|
300
313
|
|
|
@@ -343,24 +356,15 @@ export default class InnerRouter {
|
|
|
343
356
|
|
|
344
357
|
req.index = (current?.index ?? 0) + 1;
|
|
345
358
|
this.onRoute(req, () => {
|
|
346
|
-
|
|
359
|
+
const url = req.url;
|
|
360
|
+
this.history.pushState(
|
|
361
|
+
req._toState(),
|
|
362
|
+
url.pathname + url.search + url.hash,
|
|
363
|
+
);
|
|
364
|
+
this.route = req.toRoute();
|
|
347
365
|
});
|
|
348
366
|
}
|
|
349
367
|
|
|
350
|
-
/**
|
|
351
|
-
* Sets a route
|
|
352
|
-
*
|
|
353
|
-
* Will trigger an onRoute event but will not store any scroll progress
|
|
354
|
-
* or modify the history
|
|
355
|
-
*
|
|
356
|
-
* @param req
|
|
357
|
-
*/
|
|
358
|
-
setRoute(req: Request, preventOnRoute = false) {
|
|
359
|
-
this.route = req.toRoute();
|
|
360
|
-
|
|
361
|
-
if (!preventOnRoute) this.onRoute(req, () => {});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
368
|
/**
|
|
365
369
|
* This pushes a new route to the history
|
|
366
370
|
*
|
|
@@ -369,7 +373,7 @@ export default class InnerRouter {
|
|
|
369
373
|
* ## Important
|
|
370
374
|
* Make sure the route has the correct origin
|
|
371
375
|
*/
|
|
372
|
-
push(req: Request
|
|
376
|
+
push(req: Request) {
|
|
373
377
|
const url = req.url;
|
|
374
378
|
// todo a push should also store the previous scrollY
|
|
375
379
|
|
|
@@ -383,12 +387,13 @@ export default class InnerRouter {
|
|
|
383
387
|
nReq.scrollY = this.history.scrollY();
|
|
384
388
|
}
|
|
385
389
|
|
|
386
|
-
this.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
this.onRoute(req, () => {
|
|
391
|
+
this.history.pushState(
|
|
392
|
+
req._toState(),
|
|
393
|
+
url.pathname + url.search + url.hash,
|
|
394
|
+
);
|
|
395
|
+
this.route = req.toRoute();
|
|
396
|
+
});
|
|
392
397
|
}
|
|
393
398
|
|
|
394
399
|
/**
|
|
@@ -413,12 +418,13 @@ export default class InnerRouter {
|
|
|
413
418
|
nReq.scrollY = this.history.scrollY();
|
|
414
419
|
}
|
|
415
420
|
|
|
416
|
-
this.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
421
|
+
this.onRoute(req, () => {
|
|
422
|
+
this.history.replaceState(
|
|
423
|
+
req._toState(),
|
|
424
|
+
url.pathname + url.search + url.hash,
|
|
425
|
+
);
|
|
426
|
+
this.route = req.toRoute();
|
|
427
|
+
});
|
|
422
428
|
}
|
|
423
429
|
|
|
424
430
|
/**
|
package/src/routing/Request.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Barrier } from 'crelte-std/sync';
|
|
2
2
|
import Route, { RouteOrigin } from './Route.js';
|
|
3
3
|
import Site from './Site.js';
|
|
4
|
+
import { objClone } from '../utils.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Options to create a Request
|
|
@@ -9,6 +10,8 @@ export type RequestOptions = {
|
|
|
9
10
|
scrollY?: number;
|
|
10
11
|
index?: number;
|
|
11
12
|
origin?: RouteOrigin;
|
|
13
|
+
state?: Record<string, any>;
|
|
14
|
+
context?: Record<string, any>;
|
|
12
15
|
disableScroll?: boolean;
|
|
13
16
|
disableLoadData?: boolean;
|
|
14
17
|
statusCode?: number;
|
|
@@ -60,6 +63,8 @@ export default class Request extends Route {
|
|
|
60
63
|
scrollY: route.scrollY ?? undefined,
|
|
61
64
|
index: route.index,
|
|
62
65
|
origin: route.origin,
|
|
66
|
+
state: route._state,
|
|
67
|
+
context: route._context,
|
|
63
68
|
...opts,
|
|
64
69
|
});
|
|
65
70
|
}
|
|
@@ -104,6 +109,8 @@ export default class Request extends Route {
|
|
|
104
109
|
scrollY: this.scrollY ?? undefined,
|
|
105
110
|
index: this.index,
|
|
106
111
|
origin: this.origin,
|
|
112
|
+
state: objClone(this._state),
|
|
113
|
+
context: this._context,
|
|
107
114
|
disableScroll: this.disableScroll,
|
|
108
115
|
statusCode: this.statusCode ?? undefined,
|
|
109
116
|
});
|
|
@@ -117,6 +124,8 @@ export default class Request extends Route {
|
|
|
117
124
|
scrollY: this.scrollY ?? undefined,
|
|
118
125
|
index: this.index,
|
|
119
126
|
origin: this.origin,
|
|
127
|
+
state: objClone(this._state),
|
|
128
|
+
context: this._context,
|
|
120
129
|
});
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -125,6 +134,8 @@ export default class Request extends Route {
|
|
|
125
134
|
this.scrollY = opts.scrollY ?? this.scrollY;
|
|
126
135
|
this.index = opts.index ?? this.index;
|
|
127
136
|
this.origin = opts.origin ?? this.origin;
|
|
137
|
+
this._state = opts.state ?? this._state;
|
|
138
|
+
this._context = opts.context ?? this._context;
|
|
128
139
|
this.disableScroll = opts.disableScroll ?? this.disableScroll;
|
|
129
140
|
this.statusCode = opts.statusCode ?? this.statusCode;
|
|
130
141
|
}
|
package/src/routing/Route.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { objClone } from '../utils.js';
|
|
1
2
|
import Site from './Site.js';
|
|
2
3
|
import { trimSlashEnd } from './utils.js';
|
|
3
4
|
|
|
@@ -5,6 +6,8 @@ export type RouteOptions = {
|
|
|
5
6
|
scrollY?: number;
|
|
6
7
|
index?: number;
|
|
7
8
|
origin?: RouteOrigin;
|
|
9
|
+
state?: Record<string, any>;
|
|
10
|
+
context?: Record<string, any>;
|
|
8
11
|
};
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -74,6 +77,25 @@ export default class Route {
|
|
|
74
77
|
*/
|
|
75
78
|
origin: RouteOrigin;
|
|
76
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @hidden
|
|
82
|
+
* State data that can be used to store additional information
|
|
83
|
+
*/
|
|
84
|
+
_state: Record<string, any>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @hidden
|
|
88
|
+
* Any data that should be passed to onRoute and onRequest handlers
|
|
89
|
+
* or exchanged between loadData's
|
|
90
|
+
* This context is not persistant and should be considered "valid"
|
|
91
|
+
* only for the current request / route
|
|
92
|
+
*
|
|
93
|
+
* ## Note
|
|
94
|
+
* Consider using state instead. This will not be cloned in the clone
|
|
95
|
+
* call so will always be the same object
|
|
96
|
+
*/
|
|
97
|
+
_context: Record<string, any>;
|
|
98
|
+
|
|
77
99
|
/**
|
|
78
100
|
* Creates a new Route
|
|
79
101
|
*/
|
|
@@ -84,6 +106,8 @@ export default class Route {
|
|
|
84
106
|
this.scrollY = opts.scrollY ?? null;
|
|
85
107
|
this.index = opts.index ?? 0;
|
|
86
108
|
this.origin = opts.origin ?? 'manual';
|
|
109
|
+
this._state = opts.state ?? {};
|
|
110
|
+
this._context = opts.context ?? {};
|
|
87
111
|
}
|
|
88
112
|
|
|
89
113
|
/**
|
|
@@ -184,7 +208,8 @@ export default class Route {
|
|
|
184
208
|
}
|
|
185
209
|
|
|
186
210
|
/**
|
|
187
|
-
* Sets the search param or removes it if the value is null or
|
|
211
|
+
* Sets the search param or removes it if the value is null, undefined or an
|
|
212
|
+
* empty string
|
|
188
213
|
*
|
|
189
214
|
* ## Example
|
|
190
215
|
* ```
|
|
@@ -197,13 +222,69 @@ export default class Route {
|
|
|
197
222
|
* ```
|
|
198
223
|
*/
|
|
199
224
|
setSearchParam(key: string, value?: string | number | null) {
|
|
200
|
-
|
|
225
|
+
const deleteValue =
|
|
226
|
+
typeof value === 'undefined' ||
|
|
227
|
+
value === null ||
|
|
228
|
+
(typeof value === 'string' && value === '');
|
|
229
|
+
|
|
230
|
+
if (!deleteValue) {
|
|
201
231
|
this.search.set(key, value as string);
|
|
202
232
|
} else {
|
|
203
233
|
this.search.delete(key);
|
|
204
234
|
}
|
|
205
235
|
}
|
|
206
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Returns a state value if it exists.
|
|
239
|
+
*/
|
|
240
|
+
getState<T = any>(key: string): T | null {
|
|
241
|
+
return this._state[key] ?? null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Sets a state value.
|
|
246
|
+
* If the value is null or undefined, the key will be removed.
|
|
247
|
+
*
|
|
248
|
+
* ## When to use state
|
|
249
|
+
* State is used to store additional information that persists across route changes.
|
|
250
|
+
* The State is only available in the client code since it is stored using window.history.
|
|
251
|
+
*
|
|
252
|
+
* Consider using setSearchParam instead to enable server side rendering.
|
|
253
|
+
*/
|
|
254
|
+
setState<T>(key: string, value: T | null | undefined) {
|
|
255
|
+
if (typeof value === 'undefined' || value === null) {
|
|
256
|
+
delete this._state[key];
|
|
257
|
+
} else {
|
|
258
|
+
this._state[key] = value;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Returns a context value if it exists.
|
|
264
|
+
*/
|
|
265
|
+
getContext<T = any>(key: string): T | null {
|
|
266
|
+
return this._context[key] ?? null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Sets a context value.
|
|
271
|
+
* If the value is null or undefined, the key will be removed.
|
|
272
|
+
*
|
|
273
|
+
* ## When to use context
|
|
274
|
+
* Context is used to pass data to onRoute and onRequest handlers or exchange data between loadData calls.
|
|
275
|
+
* This context is not persistent and should be considered valid only for the current request/route.
|
|
276
|
+
* The context is not cloned in the clone call and will be the same object.
|
|
277
|
+
*/
|
|
278
|
+
setContext<T>(key: string, value: T | null | undefined) {
|
|
279
|
+
if (typeof value === 'undefined' || value === null) {
|
|
280
|
+
delete this._context[key];
|
|
281
|
+
} else {
|
|
282
|
+
this._context[key] = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Returns true if the route is in live preview mode
|
|
287
|
+
*/
|
|
207
288
|
inLivePreview(): boolean {
|
|
208
289
|
return !!this.search.get('x-craft-live-preview');
|
|
209
290
|
}
|
|
@@ -229,6 +310,9 @@ export default class Route {
|
|
|
229
310
|
*
|
|
230
311
|
* This checks all properties of the url but search params do not have to be
|
|
231
312
|
* in the same order
|
|
313
|
+
*
|
|
314
|
+
* ## Note
|
|
315
|
+
* This does not check the state or context
|
|
232
316
|
*/
|
|
233
317
|
eq(route: Route | null) {
|
|
234
318
|
return (
|
|
@@ -288,6 +372,8 @@ export default class Route {
|
|
|
288
372
|
scrollY: this.scrollY ?? undefined,
|
|
289
373
|
index: this.index,
|
|
290
374
|
origin: this.origin,
|
|
375
|
+
state: objClone(this._state),
|
|
376
|
+
context: this._context,
|
|
291
377
|
});
|
|
292
378
|
}
|
|
293
379
|
|
|
@@ -298,6 +384,10 @@ export default class Route {
|
|
|
298
384
|
|
|
299
385
|
if (typeof state?.route?.index === 'number')
|
|
300
386
|
this.index = state.route.index;
|
|
387
|
+
|
|
388
|
+
if (typeof state?.state === 'object' && state.state !== null) {
|
|
389
|
+
this._state = state.state;
|
|
390
|
+
}
|
|
301
391
|
}
|
|
302
392
|
|
|
303
393
|
/** @hidden */
|
|
@@ -307,6 +397,7 @@ export default class Route {
|
|
|
307
397
|
scrollY: this.scrollY,
|
|
308
398
|
index: this.index,
|
|
309
399
|
},
|
|
400
|
+
state: this._state,
|
|
310
401
|
};
|
|
311
402
|
}
|
|
312
403
|
}
|