crelte 0.2.0 → 0.2.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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/init/client.d.ts.map +1 -1
- package/dist/init/client.js +6 -0
- package/dist/routing/InnerRouter.d.ts +21 -15
- package/dist/routing/InnerRouter.d.ts.map +1 -1
- package/dist/routing/InnerRouter.js +76 -49
- package/dist/routing/Request.d.ts +6 -0
- package/dist/routing/Request.d.ts.map +1 -1
- package/dist/routing/Request.js +6 -0
- package/dist/routing/Route.d.ts +40 -17
- package/dist/routing/Route.d.ts.map +1 -1
- package/dist/routing/Route.js +69 -41
- package/dist/routing/Router.d.ts +30 -8
- package/dist/routing/Router.d.ts.map +1 -1
- package/dist/routing/Router.js +80 -19
- package/package.json +1 -1
- package/src/index.ts +9 -0
- package/src/init/client.ts +9 -0
- package/src/routing/InnerRouter.ts +81 -57
- package/src/routing/Request.ts +8 -0
- package/src/routing/Route.ts +91 -52
- package/src/routing/Router.ts +96 -23
- package/LICENSE.md +0 -41
package/src/routing/Route.ts
CHANGED
|
@@ -13,23 +13,25 @@ export type RouteOptions = {
|
|
|
13
13
|
*
|
|
14
14
|
* - `'init'`: is set on the first page load
|
|
15
15
|
* - `'manual'`: is set when a route is triggered manually via `Router.open`
|
|
16
|
-
* - `'live-preview-init'`: is set on the first page load in live preview mode
|
|
17
16
|
* - `'click'`: is set when a route is triggered by a click event
|
|
18
17
|
* - `'pop'`: is set when a route is triggered by a popstate event (back/forward)
|
|
18
|
+
* - `'replace'`: is set when a route is replaced via `Router.replaceState`
|
|
19
|
+
* - `'push'`: is set when a route is pushed via `Router.pushState`
|
|
20
|
+
*
|
|
21
|
+
* ## Note
|
|
22
|
+
* `replace` and `push` will not call loadData
|
|
19
23
|
*/
|
|
20
24
|
export type RouteOrigin =
|
|
21
25
|
| 'init'
|
|
22
|
-
| 'live-preview-init'
|
|
23
26
|
| 'manual'
|
|
24
27
|
| 'click'
|
|
25
|
-
| 'pop'
|
|
28
|
+
| 'pop'
|
|
29
|
+
| 'replace'
|
|
30
|
+
| 'push';
|
|
26
31
|
|
|
27
32
|
/**
|
|
28
33
|
* A Route contains information about the current page for example the url and
|
|
29
|
-
* the site
|
|
30
|
-
*
|
|
31
|
-
* ## Note
|
|
32
|
-
* Never update the route directly, clone it before
|
|
34
|
+
* the site
|
|
33
35
|
*/
|
|
34
36
|
export default class Route {
|
|
35
37
|
/**
|
|
@@ -51,6 +53,13 @@ export default class Route {
|
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* The scroll position of the current route
|
|
56
|
+
*
|
|
57
|
+
* ## Note
|
|
58
|
+
* This does not have to represent the current scroll position
|
|
59
|
+
* should more be used internally.
|
|
60
|
+
*
|
|
61
|
+
* It might be useful for a new request to specify the wanted
|
|
62
|
+
* scroll position
|
|
54
63
|
*/
|
|
55
64
|
scrollY: number | null;
|
|
56
65
|
|
|
@@ -154,6 +163,51 @@ export default class Route {
|
|
|
154
163
|
return this.url.hash;
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Checks if there are previous routes which would allow it to go back
|
|
168
|
+
*/
|
|
169
|
+
canGoBack(): boolean {
|
|
170
|
+
return !!this.index;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets the search param
|
|
175
|
+
*
|
|
176
|
+
* ## Example
|
|
177
|
+
* ```
|
|
178
|
+
* const route = new Route('https://example.com/foo/bar/?a=1&b=2', null);
|
|
179
|
+
* console.log(route.getSearchParam('a')); // '1'
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
getSearchParam(key: string): string | null {
|
|
183
|
+
return this.search.get(key);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sets the search param or removes it if the value is null or undefined
|
|
188
|
+
*
|
|
189
|
+
* ## Example
|
|
190
|
+
* ```
|
|
191
|
+
* const route = new Route('https://example.com/foo/bar/?a=1&b=2', null);
|
|
192
|
+
* route.setSearchParam('a', '3');
|
|
193
|
+
* console.log(route.url.href); // 'https://example.com/foo/bar/?a=3&b=2'
|
|
194
|
+
*
|
|
195
|
+
* route.setSearchParam('a', null);
|
|
196
|
+
* console.log(route.url.href); // 'https://example.com/foo/bar/?b=2'
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
setSearchParam(key: string, value?: string | number | null) {
|
|
200
|
+
if (typeof value !== 'undefined' && value !== null) {
|
|
201
|
+
this.search.set(key, value as string);
|
|
202
|
+
} else {
|
|
203
|
+
this.search.delete(key);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
inLivePreview(): boolean {
|
|
208
|
+
return !!this.search.get('x-craft-live-preview');
|
|
209
|
+
}
|
|
210
|
+
|
|
157
211
|
/**
|
|
158
212
|
* Returns if the site matches the url
|
|
159
213
|
*/
|
|
@@ -176,7 +230,32 @@ export default class Route {
|
|
|
176
230
|
* This checks all properties of the url but search params do not have to be
|
|
177
231
|
* in the same order
|
|
178
232
|
*/
|
|
179
|
-
eq(route: Route) {
|
|
233
|
+
eq(route: Route | null) {
|
|
234
|
+
return (
|
|
235
|
+
route &&
|
|
236
|
+
this.eqUrl(route) &&
|
|
237
|
+
this.eqSearch(route) &&
|
|
238
|
+
this.eqHash(route)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Checks if the route is equal to another route
|
|
244
|
+
*
|
|
245
|
+
* This does not check the search params or hash
|
|
246
|
+
*/
|
|
247
|
+
eqUrl(route: Route | null) {
|
|
248
|
+
return (
|
|
249
|
+
route &&
|
|
250
|
+
this.url.pathname === route.url.pathname &&
|
|
251
|
+
this.url.origin === route.url.origin
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Checks if the search params are equal to another route
|
|
257
|
+
*/
|
|
258
|
+
eqSearch(route: Route | null) {
|
|
180
259
|
const searchEq = (a: URLSearchParams, b: URLSearchParams) => {
|
|
181
260
|
if (a.size !== b.size) return false;
|
|
182
261
|
|
|
@@ -191,54 +270,14 @@ export default class Route {
|
|
|
191
270
|
.every(([[ak, av], [bk, bv]]) => ak === bk && av === bv);
|
|
192
271
|
};
|
|
193
272
|
|
|
194
|
-
return (
|
|
195
|
-
route &&
|
|
196
|
-
this.url.pathname === route.url.pathname &&
|
|
197
|
-
this.url.origin === route.url.origin &&
|
|
198
|
-
searchEq(this.search, route.search) &&
|
|
199
|
-
this.hash === route.hash
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Checks if there are previous routes which would allow it to go back
|
|
205
|
-
*/
|
|
206
|
-
canGoBack(): boolean {
|
|
207
|
-
return !!this.index;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Gets the search param
|
|
212
|
-
*
|
|
213
|
-
* ## Example
|
|
214
|
-
* ```
|
|
215
|
-
* const route = new Route('https://example.com/foo/bar/?a=1&b=2', null);
|
|
216
|
-
* console.log(route.getSearchParam('a')); // '1'
|
|
217
|
-
* ```
|
|
218
|
-
*/
|
|
219
|
-
getSearchParam(key: string): string | null {
|
|
220
|
-
return this.search.get(key);
|
|
273
|
+
return route && searchEq(this.search, route.search);
|
|
221
274
|
}
|
|
222
275
|
|
|
223
276
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* ## Example
|
|
227
|
-
* ```
|
|
228
|
-
* const route = new Route('https://example.com/foo/bar/?a=1&b=2', null);
|
|
229
|
-
* route.setSearchParam('a', '3');
|
|
230
|
-
* console.log(route.url.href); // 'https://example.com/foo/bar/?a=3&b=2'
|
|
231
|
-
*
|
|
232
|
-
* route.setSearchParam('a', null);
|
|
233
|
-
* console.log(route.url.href); // 'https://example.com/foo/bar/?b=2'
|
|
234
|
-
* ```
|
|
277
|
+
* Checks if the hash is equal to another route
|
|
235
278
|
*/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.search.set(key, value as string);
|
|
239
|
-
} else {
|
|
240
|
-
this.search.delete(key);
|
|
241
|
-
}
|
|
279
|
+
eqHash(route: Route | null) {
|
|
280
|
+
return route && this.hash === route.hash;
|
|
242
281
|
}
|
|
243
282
|
|
|
244
283
|
/**
|
package/src/routing/Router.ts
CHANGED
|
@@ -35,6 +35,17 @@ type Internal = {
|
|
|
35
35
|
ready: () => any,
|
|
36
36
|
) => void;
|
|
37
37
|
|
|
38
|
+
// onNothingLoaded get's called if the request did not load new Data
|
|
39
|
+
// since maybe a push or replace was called
|
|
40
|
+
onNothingLoaded: (
|
|
41
|
+
req: Request,
|
|
42
|
+
// call ready once your ready to update the dom
|
|
43
|
+
// this makes sure we trigger a route and site update
|
|
44
|
+
// almost at the same moment and probably the same tick
|
|
45
|
+
// to make sure we don't have any flickering
|
|
46
|
+
ready: () => void,
|
|
47
|
+
) => void;
|
|
48
|
+
|
|
38
49
|
onLoad: LoadFn;
|
|
39
50
|
|
|
40
51
|
domReady: (req: Request) => void;
|
|
@@ -78,8 +89,6 @@ export default class Router {
|
|
|
78
89
|
*/
|
|
79
90
|
private _loadingProgress: Writable<number>;
|
|
80
91
|
|
|
81
|
-
private _onRouteEv: Listeners<[Route]>;
|
|
82
|
-
|
|
83
92
|
private _onRequest: Listeners<[Request]>;
|
|
84
93
|
|
|
85
94
|
/** @hidden */
|
|
@@ -105,12 +114,11 @@ export default class Router {
|
|
|
105
114
|
this._loading = new Writable(false);
|
|
106
115
|
this._loadingProgress = new Writable(0);
|
|
107
116
|
|
|
108
|
-
this._onRouteEv = new Listeners();
|
|
109
|
-
|
|
110
117
|
this._onRequest = new Listeners();
|
|
111
118
|
|
|
112
119
|
this._internal = {
|
|
113
120
|
onLoaded: () => {},
|
|
121
|
+
onNothingLoaded: () => {},
|
|
114
122
|
onLoad: () => {},
|
|
115
123
|
domReady: req => this.inner.domReady(req),
|
|
116
124
|
initClient: () => this._initClient(),
|
|
@@ -167,10 +175,13 @@ export default class Router {
|
|
|
167
175
|
/**
|
|
168
176
|
* Open a new route
|
|
169
177
|
*
|
|
170
|
-
* @param target the target to open can be an url or a
|
|
178
|
+
* @param target the target to open can be an url, a route or a request
|
|
171
179
|
* the url needs to start with http or with a / which will be considered as
|
|
172
180
|
* the site baseUrl
|
|
173
181
|
*
|
|
182
|
+
* ## Note
|
|
183
|
+
* The origin will always be set to 'manual'
|
|
184
|
+
*
|
|
174
185
|
* ## Example
|
|
175
186
|
* ```
|
|
176
187
|
* import { getRouter } from 'crelte';
|
|
@@ -182,16 +193,25 @@ export default class Router {
|
|
|
182
193
|
* // the following page will be opened https://example.com/de/foo/bar
|
|
183
194
|
* ```
|
|
184
195
|
*/
|
|
185
|
-
open(target: string | URL | Route, opts: RequestOptions = {}) {
|
|
186
|
-
this.inner.
|
|
196
|
+
open(target: string | URL | Route | Request, opts: RequestOptions = {}) {
|
|
197
|
+
const req = this.inner.targetToRequest(target, {
|
|
198
|
+
...opts,
|
|
199
|
+
origin: 'manual',
|
|
200
|
+
});
|
|
201
|
+
this.inner.open(req);
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
/**
|
|
190
|
-
* This pushes the
|
|
205
|
+
* This pushes the new route without triggering a new pageload
|
|
191
206
|
*
|
|
192
207
|
* You can use this when using pagination for example change the route object
|
|
193
208
|
* (search argument) and then call pushState
|
|
194
209
|
*
|
|
210
|
+
* ## Note
|
|
211
|
+
* This will always set the origin to 'push'
|
|
212
|
+
* And will clear the scrollY value if you not provide a new one via the `opts`
|
|
213
|
+
* This will disableLoadData by default if you not provide an override via the `opts`
|
|
214
|
+
*
|
|
195
215
|
* ## Example
|
|
196
216
|
* ```
|
|
197
217
|
* import { getRouter } from 'crelte';
|
|
@@ -204,18 +224,37 @@ export default class Router {
|
|
|
204
224
|
* router.pushState(route);
|
|
205
225
|
* ```
|
|
206
226
|
*/
|
|
207
|
-
|
|
227
|
+
push(route: Route | Request, opts: RequestOptions = {}) {
|
|
228
|
+
// cancel previous request
|
|
208
229
|
this.pageLoader.discard();
|
|
209
|
-
this.inner.
|
|
230
|
+
const req = this.inner.targetToRequest(route, {
|
|
231
|
+
...opts,
|
|
232
|
+
origin: 'push',
|
|
233
|
+
scrollY: opts.scrollY ?? undefined,
|
|
234
|
+
disableLoadData: opts.disableLoadData ?? true,
|
|
235
|
+
});
|
|
236
|
+
this.inner.push(req);
|
|
210
237
|
this.destroyRequest();
|
|
211
238
|
this.setNewRoute(route);
|
|
212
239
|
}
|
|
213
240
|
|
|
241
|
+
/**
|
|
242
|
+
* @deprecated use push instead
|
|
243
|
+
*/
|
|
244
|
+
pushState(route: Route | Request) {
|
|
245
|
+
this.push(route);
|
|
246
|
+
}
|
|
247
|
+
|
|
214
248
|
/**
|
|
215
249
|
* This replaces the state of the route without triggering an event
|
|
216
250
|
*
|
|
217
251
|
* You can use this when using some filters for example a search filter
|
|
218
252
|
*
|
|
253
|
+
* ## Note
|
|
254
|
+
* This will always set the origin to 'replace'
|
|
255
|
+
* And will clear the scrollY value if you not provide a new one via the `opts`
|
|
256
|
+
* This will disableLoadData by default if you not provide an override via the `opts`
|
|
257
|
+
*
|
|
219
258
|
* ## Example
|
|
220
259
|
* ```
|
|
221
260
|
* import { getRouter } from 'crelte';
|
|
@@ -228,11 +267,24 @@ export default class Router {
|
|
|
228
267
|
* router.replaceState(route);
|
|
229
268
|
* ```
|
|
230
269
|
*/
|
|
231
|
-
|
|
270
|
+
replace(route: Route | Request, opts: RequestOptions = {}) {
|
|
271
|
+
// cancel previous request
|
|
232
272
|
this.pageLoader.discard();
|
|
233
|
-
this.inner.
|
|
273
|
+
const req = this.inner.targetToRequest(route, {
|
|
274
|
+
origin: 'replace',
|
|
275
|
+
scrollY: opts.scrollY ?? undefined,
|
|
276
|
+
disableLoadData: opts.disableLoadData ?? true,
|
|
277
|
+
});
|
|
278
|
+
this.inner.replace(req);
|
|
234
279
|
this.destroyRequest();
|
|
235
|
-
this.setNewRoute(
|
|
280
|
+
this.setNewRoute(req);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @deprecated use replace instead
|
|
285
|
+
*/
|
|
286
|
+
replaceState(route: Route | Request) {
|
|
287
|
+
this.replace(route);
|
|
236
288
|
}
|
|
237
289
|
|
|
238
290
|
/**
|
|
@@ -259,13 +311,13 @@ export default class Router {
|
|
|
259
311
|
/**
|
|
260
312
|
* Add a listener for the onRoute event
|
|
261
313
|
*
|
|
262
|
-
* This
|
|
263
|
-
*
|
|
314
|
+
* This will trigger every time a new route is set
|
|
315
|
+
* and is equivalent to router.route.subscribe(fn)
|
|
264
316
|
*
|
|
265
317
|
* @returns a function to remove the listener
|
|
266
318
|
*/
|
|
267
319
|
onRoute(fn: (route: Route) => void): () => void {
|
|
268
|
-
return this.
|
|
320
|
+
return this.route.subscribe(fn);
|
|
269
321
|
}
|
|
270
322
|
|
|
271
323
|
/**
|
|
@@ -297,6 +349,10 @@ export default class Router {
|
|
|
297
349
|
): Promise<ServerInited> {
|
|
298
350
|
this.inner.initServer();
|
|
299
351
|
|
|
352
|
+
this._internal.onNothingLoaded = (_req, ready) => {
|
|
353
|
+
ready();
|
|
354
|
+
};
|
|
355
|
+
|
|
300
356
|
const prom: Promise<ServerInited> = new Promise(resolve => {
|
|
301
357
|
this._internal.onLoaded = (success, req, ready) => {
|
|
302
358
|
const props = ready();
|
|
@@ -360,7 +416,11 @@ export default class Router {
|
|
|
360
416
|
this._onRequest.trigger(req);
|
|
361
417
|
|
|
362
418
|
// route prepared
|
|
363
|
-
|
|
419
|
+
if (!req.disableLoadData) {
|
|
420
|
+
this.pageLoader.load(req, { changeHistory });
|
|
421
|
+
} else {
|
|
422
|
+
this._onNothingLoaded(req, { changeHistory });
|
|
423
|
+
}
|
|
364
424
|
}
|
|
365
425
|
|
|
366
426
|
private destroyRequest() {
|
|
@@ -389,15 +449,28 @@ export default class Router {
|
|
|
389
449
|
|
|
390
450
|
const route = req.toRoute();
|
|
391
451
|
|
|
392
|
-
|
|
452
|
+
// call the client or server saying we are ready for a new render
|
|
453
|
+
this._internal.onLoaded(resp.success, req, () => {
|
|
393
454
|
this.setNewRoute(route);
|
|
455
|
+
return resp.data;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
394
458
|
|
|
395
|
-
|
|
396
|
-
|
|
459
|
+
private async _onNothingLoaded(req: Request, more: LoadedMore) {
|
|
460
|
+
// check if the render was cancelled
|
|
461
|
+
if (await req._renderBarrier.ready()) return;
|
|
397
462
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
463
|
+
// when the data is loaded let's update the route of the inner
|
|
464
|
+
// this is will only happen if no other route has been requested
|
|
465
|
+
// in the meantime
|
|
466
|
+
more.changeHistory();
|
|
467
|
+
|
|
468
|
+
const route = req.toRoute();
|
|
469
|
+
|
|
470
|
+
// call the client or server saying there was an update in the route
|
|
471
|
+
// but no new data was loaded so no render should happen
|
|
472
|
+
this._internal.onNothingLoaded(req, () => {
|
|
473
|
+
this.setNewRoute(route);
|
|
401
474
|
});
|
|
402
475
|
}
|
|
403
476
|
|
package/LICENSE.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
Copyright © Dunkel
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Permission is hereby granted to any person obtaining a copy of this software
|
|
5
|
-
(the “Software”) to use, copy, modify, merge, publish and/or distribute copies
|
|
6
|
-
of the Software, and to permit persons to whom the Software is furnished to do
|
|
7
|
-
so, subject to the following conditions:
|
|
8
|
-
|
|
9
|
-
1. **Don’t plagiarize.** The above copyright notice and this license shall be
|
|
10
|
-
included in all copies or substantial portions of the Software.
|
|
11
|
-
|
|
12
|
-
2. **Don’t use the same license on more than one project.** Each licensed copy
|
|
13
|
-
of the Software shall be actively installed in no more than one production
|
|
14
|
-
environment at a time.
|
|
15
|
-
|
|
16
|
-
3. **Don’t mess with the licensing features.** Software features related to
|
|
17
|
-
licensing shall not be altered or circumvented in any way, including (but
|
|
18
|
-
not limited to) license validation, payment prompts, feature restrictions,
|
|
19
|
-
and update eligibility.
|
|
20
|
-
|
|
21
|
-
4. **Pay up.** Payment shall be made immediately upon receipt of any notice,
|
|
22
|
-
prompt, reminder, or other message indicating that a payment is owed.
|
|
23
|
-
|
|
24
|
-
5. **Follow the law.** All use of the Software shall not violate any applicable
|
|
25
|
-
law or regulation, nor infringe the rights of any other person or entity.
|
|
26
|
-
|
|
27
|
-
Failure to comply with the foregoing conditions will automatically and
|
|
28
|
-
immediately result in termination of the permission granted hereby. This
|
|
29
|
-
license does not include any right to receive updates to the Software or
|
|
30
|
-
technical support. Licensees bear all risk related to the quality and
|
|
31
|
-
performance of the Software and any modifications made or obtained to it,
|
|
32
|
-
including liability for actual and consequential harm, such as loss or
|
|
33
|
-
corruption of data, and any necessary service, repair, or correction.
|
|
34
|
-
|
|
35
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
36
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
37
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
38
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
39
|
-
LIABILITY, INCLUDING SPECIAL, INCIDENTAL AND CONSEQUENTIAL DAMAGES, WHETHER IN
|
|
40
|
-
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
41
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|