@hybridly/core 0.10.0-beta.11 → 0.10.0-beta.13
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/_chunks/chunk.mjs +2 -1
- package/dist/index.mjs +167 -3
- package/package.json +2 -2
package/dist/_chunks/chunk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __exportAll = (all, no_symbols) => {
|
|
4
4
|
let target = {};
|
|
@@ -9,4 +9,5 @@ var __exportAll = (all, no_symbols) => {
|
|
|
9
9
|
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
10
|
return target;
|
|
11
11
|
};
|
|
12
|
+
//#endregion
|
|
12
13
|
export { __exportAll as t };
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { debounce, debug, hasFiles, match, merge, objectToFormData, random, remo
|
|
|
3
3
|
import axios from "axios";
|
|
4
4
|
import { parse, stringify } from "superjson";
|
|
5
5
|
import qs from "qs";
|
|
6
|
+
//#region src/constants.ts
|
|
6
7
|
var constants_exports = /* @__PURE__ */ __exportAll({
|
|
7
8
|
DIALOG_KEY_HEADER: () => DIALOG_KEY_HEADER,
|
|
8
9
|
DIALOG_REDIRECT_HEADER: () => DIALOG_REDIRECT_HEADER,
|
|
@@ -29,6 +30,8 @@ const EXCEPT_DATA_HEADER = `${HYBRIDLY_HEADER}-except-data`;
|
|
|
29
30
|
const VERSION_HEADER = `${HYBRIDLY_HEADER}-version`;
|
|
30
31
|
const ERROR_BAG_HEADER = `${HYBRIDLY_HEADER}-error-bag`;
|
|
31
32
|
const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/errors.ts
|
|
32
35
|
var NotAHybridResponseError = class extends Error {
|
|
33
36
|
constructor(response) {
|
|
34
37
|
super();
|
|
@@ -51,6 +54,8 @@ var MissingRouteParameter = class extends Error {
|
|
|
51
54
|
super(`Parameter [${parameter}] is required for route [${routeName}].`);
|
|
52
55
|
}
|
|
53
56
|
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/plugins/plugin.ts
|
|
54
59
|
function definePlugin(plugin) {
|
|
55
60
|
return plugin;
|
|
56
61
|
}
|
|
@@ -87,6 +92,11 @@ async function runHooks(hook, requestHooks, ...args) {
|
|
|
87
92
|
debug.hook(`Called all hooks for [${hook}],`, result);
|
|
88
93
|
return !result.includes(false);
|
|
89
94
|
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/plugins/hooks.ts
|
|
97
|
+
/**
|
|
98
|
+
* Registers a global hook.
|
|
99
|
+
*/
|
|
90
100
|
function appendCallbackToHooks(hook, fn) {
|
|
91
101
|
const hooks = getRouterContext().hooks;
|
|
92
102
|
hooks[hook] = [...hooks[hook] ?? [], fn];
|
|
@@ -95,6 +105,9 @@ function appendCallbackToHooks(hook, fn) {
|
|
|
95
105
|
if (index !== -1) hooks[hook]?.splice(index, 1);
|
|
96
106
|
};
|
|
97
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Registers a global hook.
|
|
110
|
+
*/
|
|
98
111
|
function registerHook(hook, fn, options) {
|
|
99
112
|
if (options?.once) {
|
|
100
113
|
const unregister = appendCallbackToHooks(hook, async (...args) => {
|
|
@@ -105,6 +118,9 @@ function registerHook(hook, fn, options) {
|
|
|
105
118
|
}
|
|
106
119
|
return appendCallbackToHooks(hook, fn);
|
|
107
120
|
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/scroll.ts
|
|
123
|
+
/** Saves the current view's scrollbar positions into the history state. */
|
|
108
124
|
function saveScrollPositions() {
|
|
109
125
|
const regions = getScrollRegions();
|
|
110
126
|
debug.scroll("Saving scroll positions of:", regions.map((el) => ({
|
|
@@ -120,9 +136,14 @@ function saveScrollPositions() {
|
|
|
120
136
|
})) });
|
|
121
137
|
setHistoryState({ replace: true });
|
|
122
138
|
}
|
|
139
|
+
/** Gets DOM elements which scroll positions should be saved. */
|
|
123
140
|
function getScrollRegions() {
|
|
124
|
-
return Array.from(document?.querySelectorAll(`[
|
|
141
|
+
return Array.from(document?.querySelectorAll(`[scroll-region]`) ?? []).concat(document.documentElement, document.body);
|
|
125
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Resets the current view's scrollbars positions to the top, and save them
|
|
145
|
+
* in the history state.
|
|
146
|
+
*/
|
|
126
147
|
function resetScrollPositions() {
|
|
127
148
|
debug.scroll("Resetting scroll positions.");
|
|
128
149
|
getScrollRegions().forEach((element) => element.scrollTo({
|
|
@@ -135,6 +156,7 @@ function resetScrollPositions() {
|
|
|
135
156
|
document.getElementById(window.location.hash.slice(1))?.scrollIntoView();
|
|
136
157
|
}
|
|
137
158
|
}
|
|
159
|
+
/** Restores the scroll positions stored in the context. */
|
|
138
160
|
async function restoreScrollPositions() {
|
|
139
161
|
const context = getRouterContext();
|
|
140
162
|
const regions = getScrollRegions();
|
|
@@ -150,9 +172,15 @@ async function restoreScrollPositions() {
|
|
|
150
172
|
}));
|
|
151
173
|
});
|
|
152
174
|
}
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/url.ts
|
|
177
|
+
/** Normalizes the given input to an URL. */
|
|
153
178
|
function normalizeUrl(href, trailingSlash) {
|
|
154
179
|
return makeUrl(href, { trailingSlash }).toString();
|
|
155
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Converts an input to an URL, optionally changing its properties after initialization.
|
|
183
|
+
*/
|
|
156
184
|
function makeUrl(href, transformations = {}) {
|
|
157
185
|
try {
|
|
158
186
|
const base = document?.location?.href === "//" ? void 0 : document.location.href;
|
|
@@ -179,6 +207,9 @@ function makeUrl(href, transformations = {}) {
|
|
|
179
207
|
throw new TypeError(`${href} is not resolvable to a valid URL.`);
|
|
180
208
|
}
|
|
181
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Checks if the given URLs have the same origin and path.
|
|
212
|
+
*/
|
|
182
213
|
function sameUrls(...hrefs) {
|
|
183
214
|
if (hrefs.length < 2) return true;
|
|
184
215
|
try {
|
|
@@ -188,6 +219,9 @@ function sameUrls(...hrefs) {
|
|
|
188
219
|
} catch {}
|
|
189
220
|
return false;
|
|
190
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Checks if the given URLs have the same origin, path, and hash.
|
|
224
|
+
*/
|
|
191
225
|
function sameHashes(...hrefs) {
|
|
192
226
|
if (hrefs.length < 2) return true;
|
|
193
227
|
try {
|
|
@@ -197,12 +231,20 @@ function sameHashes(...hrefs) {
|
|
|
197
231
|
} catch {}
|
|
198
232
|
return false;
|
|
199
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* If the back-end did not specify a hash, if the navigation specified one,
|
|
236
|
+
* and both URLs lead to the same endpoint, we update the target URL
|
|
237
|
+
* to use the hash of the initially-requested URL.
|
|
238
|
+
*/
|
|
200
239
|
function fillHash(currentUrl, targetUrl) {
|
|
201
240
|
currentUrl = makeUrl(currentUrl);
|
|
202
241
|
targetUrl = makeUrl(targetUrl);
|
|
203
242
|
if (currentUrl.hash && !targetUrl.hash && sameUrls(targetUrl, currentUrl)) targetUrl.hash = currentUrl.hash;
|
|
204
243
|
return targetUrl.toString();
|
|
205
244
|
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/router/history.ts
|
|
247
|
+
/** Puts the given context into the history state. */
|
|
206
248
|
function setHistoryState(options = {}) {
|
|
207
249
|
if (!window?.history) throw new Error("The history API is not available, so Hybridly cannot operate.");
|
|
208
250
|
const context = getRouterContext();
|
|
@@ -219,13 +261,16 @@ function setHistoryState(options = {}) {
|
|
|
219
261
|
throw error;
|
|
220
262
|
}
|
|
221
263
|
}
|
|
264
|
+
/** Gets the current history data if it exists. */
|
|
222
265
|
function getHistoryState() {
|
|
223
266
|
return getRouterContext().serializer.unserialize(window.history.state);
|
|
224
267
|
}
|
|
268
|
+
/** Gets the current history state if it exists. */
|
|
225
269
|
function getHistoryMemo(key) {
|
|
226
270
|
const state = getHistoryState();
|
|
227
271
|
return key ? state?.memo?.[key] : state?.memo;
|
|
228
272
|
}
|
|
273
|
+
/** Register history-related event listeneners. */
|
|
229
274
|
async function registerEventListeners() {
|
|
230
275
|
const context = getRouterContext();
|
|
231
276
|
debug.history("Registering [popstate] and [scroll] event listeners.");
|
|
@@ -259,13 +304,15 @@ async function registerEventListeners() {
|
|
|
259
304
|
});
|
|
260
305
|
});
|
|
261
306
|
window?.addEventListener("scroll", (event) => debounce(100, () => {
|
|
262
|
-
if ((event?.target)?.hasAttribute?.(
|
|
307
|
+
if ((event?.target)?.hasAttribute?.("scroll-region")) saveScrollPositions();
|
|
263
308
|
}), true);
|
|
264
309
|
}
|
|
310
|
+
/** Checks if the current navigation was made by going back or forward. */
|
|
265
311
|
function isBackForwardNavigation() {
|
|
266
312
|
if (!window.history.state) return false;
|
|
267
313
|
return (window.performance?.getEntriesByType("navigation").at(0))?.type === "back_forward";
|
|
268
314
|
}
|
|
315
|
+
/** Handles a navigation which was going back or forward. */
|
|
269
316
|
async function handleBackForwardNavigation() {
|
|
270
317
|
debug.router("Handling a back/forward navigation from an external URL.");
|
|
271
318
|
const context = getRouterContext();
|
|
@@ -282,6 +329,7 @@ async function handleBackForwardNavigation() {
|
|
|
282
329
|
updateHistoryState: false
|
|
283
330
|
});
|
|
284
331
|
}
|
|
332
|
+
/** Saves a value into the current history state. */
|
|
285
333
|
function remember(key, value) {
|
|
286
334
|
debug.history(`Remembering key "${key}" with value`, value);
|
|
287
335
|
setContext({ memo: {
|
|
@@ -290,6 +338,7 @@ function remember(key, value) {
|
|
|
290
338
|
} }, { propagate: false });
|
|
291
339
|
setHistoryState({ replace: true });
|
|
292
340
|
}
|
|
341
|
+
/** Serializes the context so it can be written to the history state. */
|
|
293
342
|
function serializeContext(context) {
|
|
294
343
|
return context.serializer.serialize({
|
|
295
344
|
url: context.url,
|
|
@@ -316,6 +365,8 @@ function createSerializer(options) {
|
|
|
316
365
|
}
|
|
317
366
|
};
|
|
318
367
|
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/routing/route.ts
|
|
319
370
|
function getUrlRegexForRoute(name) {
|
|
320
371
|
const routing = getRouting();
|
|
321
372
|
const definition = getRouteDefinition(name);
|
|
@@ -333,6 +384,12 @@ function getUrlRegexForRoute(name) {
|
|
|
333
384
|
});
|
|
334
385
|
return RegExp(urlRegexPattern);
|
|
335
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Check if a given URL matches a route based on its name.
|
|
389
|
+
* Additionally you can pass an object of parameters to check if the URL matches the route with the given parameters.
|
|
390
|
+
* Otherwise it will accept and thus return true for any values for the parameters defined by the route.
|
|
391
|
+
* Note: passing additional parameters that are not defined by the route or included in the current URL will cause this to return false.
|
|
392
|
+
*/
|
|
336
393
|
function urlMatchesRoute(fullUrl, name, routeParameters) {
|
|
337
394
|
const url = makeUrl(fullUrl, { hash: "" }).toString();
|
|
338
395
|
const parameters = routeParameters || {};
|
|
@@ -369,6 +426,9 @@ function getUrlFromName(name, parameters, shouldThrow) {
|
|
|
369
426
|
...transforms
|
|
370
427
|
}));
|
|
371
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Resolved the value of a route parameter from either the passed parameters or the default parameters.
|
|
431
|
+
*/
|
|
372
432
|
function getRouteParameterValue(routeName, parameterName, routeParameters) {
|
|
373
433
|
const routing = getRouting();
|
|
374
434
|
const definition = getRouteDefinition(routeName);
|
|
@@ -386,6 +446,9 @@ function getRouteParameterValue(routeName, parameterName, routeParameters) {
|
|
|
386
446
|
}
|
|
387
447
|
if (routing.defaults?.[parameterName]) return routing.defaults?.[parameterName];
|
|
388
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Gets the `UrlTransformable` object for the given route and parameters.
|
|
451
|
+
*/
|
|
389
452
|
function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
390
453
|
const definition = getRouteDefinition(routeName);
|
|
391
454
|
const parameters = routeParameters || {};
|
|
@@ -415,23 +478,44 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
|
415
478
|
})
|
|
416
479
|
};
|
|
417
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Gets the route definition.
|
|
483
|
+
*/
|
|
418
484
|
function getRouteDefinition(name) {
|
|
419
485
|
const definition = getRouting().routes[name];
|
|
420
486
|
if (!definition) throw new RouteNotFound(name);
|
|
421
487
|
return definition;
|
|
422
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Gets the routing configuration from the current context.
|
|
491
|
+
*/
|
|
423
492
|
function getRouting() {
|
|
424
493
|
const { routing } = getInternalRouterContext();
|
|
425
494
|
if (!routing) throw new RoutingNotInitialized();
|
|
426
495
|
return routing;
|
|
427
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Generates a route from the given route name.
|
|
499
|
+
*/
|
|
428
500
|
function route(name, parameters, absolute) {
|
|
429
501
|
return generateRouteFromName(name, parameters, absolute ?? getRouting().absolute ?? true);
|
|
430
502
|
}
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/routing/current.ts
|
|
431
505
|
function getCurrentUrl() {
|
|
432
506
|
if (typeof window === "undefined") return getInternalRouterContext().url;
|
|
433
507
|
return window.location.toString();
|
|
434
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Determines whether the current route matches the given name and parameters.
|
|
511
|
+
* If multiple routes match, the first one will be returned.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* currentRouteMatches('tenant.*') // matches all routes starting with 'tenant.'
|
|
516
|
+
* currentRouteMatches('tenant.*.admin') // matches all routes starting with 'tenant.' and ending with '.admin'
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
435
519
|
function currentRouteMatches(name, parameters) {
|
|
436
520
|
const namePattern = `^${name.replaceAll(".", "\\.").replaceAll("*", ".*")}$`;
|
|
437
521
|
const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
|
|
@@ -443,13 +527,19 @@ function currentRouteMatches(name, parameters) {
|
|
|
443
527
|
function getCurrentRouteName() {
|
|
444
528
|
return getNameFromUrl(getCurrentUrl());
|
|
445
529
|
}
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/routing/index.ts
|
|
446
532
|
function updateRoutingConfiguration(routing) {
|
|
447
533
|
if (!routing) return;
|
|
448
534
|
setContext({ routing });
|
|
449
535
|
}
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/download.ts
|
|
538
|
+
/** Checks if the response wants to redirect to an external URL. */
|
|
450
539
|
function isDownloadResponse(response) {
|
|
451
540
|
return response.status === 200 && !!response.headers["content-disposition"];
|
|
452
541
|
}
|
|
542
|
+
/** Handles a download. */
|
|
453
543
|
async function handleDownloadResponse(response) {
|
|
454
544
|
const blob = new Blob([response.data], { type: response.headers["content-type"] });
|
|
455
545
|
const urlObject = window.webkitURL || window.URL;
|
|
@@ -466,17 +556,22 @@ async function handleDownloadResponse(response) {
|
|
|
466
556
|
function getFileNameFromContentDispositionHeader(header) {
|
|
467
557
|
return (header.split(";")[1]?.trim().split("=")[1])?.replace(/^"(.*)"$/, "$1") ?? "";
|
|
468
558
|
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/context/context.ts
|
|
469
561
|
const state = {
|
|
470
562
|
initialized: false,
|
|
471
563
|
context: {}
|
|
472
564
|
};
|
|
565
|
+
/** Gets the current context. */
|
|
473
566
|
function getRouterContext() {
|
|
474
567
|
return getInternalRouterContext();
|
|
475
568
|
}
|
|
569
|
+
/** Gets the current context, but not in read-only. */
|
|
476
570
|
function getInternalRouterContext() {
|
|
477
571
|
if (!state.initialized) throw new Error("Hybridly is not initialized.");
|
|
478
572
|
return state.context;
|
|
479
573
|
}
|
|
574
|
+
/** Initializes the context. */
|
|
480
575
|
async function initializeContext(options) {
|
|
481
576
|
state.initialized = true;
|
|
482
577
|
state.context = {
|
|
@@ -499,6 +594,10 @@ async function initializeContext(options) {
|
|
|
499
594
|
await runHooks("initialized", {}, state.context);
|
|
500
595
|
return getInternalRouterContext();
|
|
501
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* Registers an interceptor that assumes `arraybuffer`
|
|
599
|
+
* responses and converts responses to JSON or text.
|
|
600
|
+
*/
|
|
502
601
|
function registerAxios(axios) {
|
|
503
602
|
axios.interceptors.response.use((response) => {
|
|
504
603
|
if (!isDownloadResponse(response)) {
|
|
@@ -513,6 +612,9 @@ function registerAxios(axios) {
|
|
|
513
612
|
}, (error) => Promise.reject(error));
|
|
514
613
|
return axios;
|
|
515
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* Mutates properties at the top-level of the context.
|
|
617
|
+
*/
|
|
516
618
|
function setContext(merge = {}, options = {}) {
|
|
517
619
|
Object.keys(merge).forEach((key) => {
|
|
518
620
|
Reflect.set(state.context, key, merge[key]);
|
|
@@ -523,6 +625,7 @@ function setContext(merge = {}, options = {}) {
|
|
|
523
625
|
added: merge
|
|
524
626
|
});
|
|
525
627
|
}
|
|
628
|
+
/** Gets a payload from the current context. */
|
|
526
629
|
function payloadFromContext() {
|
|
527
630
|
return {
|
|
528
631
|
url: getRouterContext().url,
|
|
@@ -531,6 +634,13 @@ function payloadFromContext() {
|
|
|
531
634
|
dialog: getRouterContext().dialog
|
|
532
635
|
};
|
|
533
636
|
}
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region src/external.ts
|
|
639
|
+
/**
|
|
640
|
+
* Performs an external navigation by saving options to the storage and
|
|
641
|
+
* making a full page reload. Upon loading, the navigation options
|
|
642
|
+
* will be pulled and a hybrid navigation will be made.
|
|
643
|
+
*/
|
|
534
644
|
async function performExternalNavigation(options) {
|
|
535
645
|
debug.external("Navigating to an external URL:", options);
|
|
536
646
|
if (options.target === "new-tab") {
|
|
@@ -549,18 +659,24 @@ async function performExternalNavigation(options) {
|
|
|
549
659
|
window.location.reload();
|
|
550
660
|
}
|
|
551
661
|
}
|
|
662
|
+
/** Navigates to the given URL without the hybrid protocol. */
|
|
552
663
|
function navigateToExternalUrl(url, data) {
|
|
553
664
|
document.location.href = makeUrl(url, { search: qs.stringify(data, {
|
|
554
665
|
encodeValuesOnly: true,
|
|
555
666
|
arrayFormat: "brackets"
|
|
556
667
|
}) }).toString();
|
|
557
668
|
}
|
|
669
|
+
/** Checks if the response wants to redirect to an external URL. */
|
|
558
670
|
function isExternalResponse(response) {
|
|
559
671
|
return response?.status === 409 && !!response?.headers?.[EXTERNAL_NAVIGATION_HEADER];
|
|
560
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Performs the internal navigation when an external navigation to a hybrid view has been made.
|
|
675
|
+
* This method is meant to be called on router creation.
|
|
676
|
+
*/
|
|
561
677
|
async function handleExternalNavigation() {
|
|
562
678
|
debug.external("Handling an external navigation.");
|
|
563
|
-
const options = JSON.parse(window.sessionStorage.getItem(
|
|
679
|
+
const options = JSON.parse(window.sessionStorage.getItem("hybridly:external") || "{}");
|
|
564
680
|
window.sessionStorage.removeItem(STORAGE_EXTERNAL_KEY);
|
|
565
681
|
debug.external("Options from the session storage:", options);
|
|
566
682
|
setContext({ url: makeUrl(getRouterContext().url, { hash: window.location.hash }).toString() });
|
|
@@ -570,12 +686,18 @@ async function handleExternalNavigation() {
|
|
|
570
686
|
preserveScroll: options.preserveScroll
|
|
571
687
|
});
|
|
572
688
|
}
|
|
689
|
+
/** Checks if the navigation being initialized points to an external location. */
|
|
573
690
|
function isExternalNavigation() {
|
|
574
691
|
try {
|
|
575
692
|
return window.sessionStorage.getItem(STORAGE_EXTERNAL_KEY) !== null;
|
|
576
693
|
} catch {}
|
|
577
694
|
return false;
|
|
578
695
|
}
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/dialog/index.ts
|
|
698
|
+
/**
|
|
699
|
+
* Closes the dialog.
|
|
700
|
+
*/
|
|
579
701
|
async function closeDialog(options) {
|
|
580
702
|
const context = getInternalRouterContext();
|
|
581
703
|
const url = context.dialog?.redirectUrl ?? context.dialog?.baseUrl;
|
|
@@ -596,18 +718,33 @@ async function closeDialog(options) {
|
|
|
596
718
|
...options
|
|
597
719
|
});
|
|
598
720
|
}
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/router/preload.ts
|
|
723
|
+
/**
|
|
724
|
+
* Checks if there is a preloaded request for the given URL.
|
|
725
|
+
*/
|
|
599
726
|
function isPreloaded(targetUrl) {
|
|
600
727
|
return getInternalRouterContext().preloadCache.has(targetUrl.toString()) ?? false;
|
|
601
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Gets the response of a preloaded request.
|
|
731
|
+
*/
|
|
602
732
|
function getPreloadedRequest(targetUrl) {
|
|
603
733
|
return getInternalRouterContext().preloadCache.get(targetUrl.toString());
|
|
604
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Stores the response of a preloaded request.
|
|
737
|
+
*/
|
|
605
738
|
function storePreloadRequest(targetUrl, response) {
|
|
606
739
|
getInternalRouterContext().preloadCache.set(targetUrl.toString(), response);
|
|
607
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* Discards a preloaded request.
|
|
743
|
+
*/
|
|
608
744
|
function discardPreloadedRequest(targetUrl) {
|
|
609
745
|
return getInternalRouterContext().preloadCache.delete(targetUrl.toString());
|
|
610
746
|
}
|
|
747
|
+
/** Preloads a hybrid request. */
|
|
611
748
|
async function performPreloadRequest(options) {
|
|
612
749
|
const context = getRouterContext();
|
|
613
750
|
const url = makeUrl(options.url ?? context.url);
|
|
@@ -637,6 +774,17 @@ async function performPreloadRequest(options) {
|
|
|
637
774
|
return false;
|
|
638
775
|
}
|
|
639
776
|
}
|
|
777
|
+
//#endregion
|
|
778
|
+
//#region src/router/router.ts
|
|
779
|
+
/**
|
|
780
|
+
* The hybridly router.
|
|
781
|
+
* This is the core function that you can use to navigate in
|
|
782
|
+
* your application. Make sure the routes you call return a
|
|
783
|
+
* hybrid response, otherwise you need to call `external`.
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* router.get('/posts/edit', { post })
|
|
787
|
+
*/
|
|
640
788
|
const router = {
|
|
641
789
|
abort: async () => getRouterContext().pendingNavigation?.controller.abort(),
|
|
642
790
|
active: () => !!getRouterContext().pendingNavigation,
|
|
@@ -700,10 +848,12 @@ const router = {
|
|
|
700
848
|
remember: (key, value) => remember(key, value)
|
|
701
849
|
}
|
|
702
850
|
};
|
|
851
|
+
/** Creates the hybridly router. */
|
|
703
852
|
async function createRouter(options) {
|
|
704
853
|
await initializeContext(options);
|
|
705
854
|
return await initializeRouter();
|
|
706
855
|
}
|
|
856
|
+
/** Performs every action necessary to make a hybrid navigation. */
|
|
707
857
|
async function performHybridNavigation(options) {
|
|
708
858
|
const navigationId = random();
|
|
709
859
|
const context = getRouterContext();
|
|
@@ -854,9 +1004,14 @@ async function performHybridNavigation(options) {
|
|
|
854
1004
|
if (context.pendingNavigation?.id === navigationId) setContext({ pendingNavigation: void 0 });
|
|
855
1005
|
}
|
|
856
1006
|
}
|
|
1007
|
+
/** Checks if the response contains a hybrid header. */
|
|
857
1008
|
function isHybridResponse(response) {
|
|
858
1009
|
return !!response?.headers[HYBRIDLY_HEADER];
|
|
859
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Makes an internal navigation that swaps the view and updates the context.
|
|
1013
|
+
* @internal
|
|
1014
|
+
*/
|
|
860
1015
|
async function navigate(options) {
|
|
861
1016
|
const context = getRouterContext();
|
|
862
1017
|
options.hasDialog ??= !!options.payload?.dialog;
|
|
@@ -966,6 +1121,7 @@ async function performHybridRequest(targetUrl, options, abortController) {
|
|
|
966
1121
|
}
|
|
967
1122
|
});
|
|
968
1123
|
}
|
|
1124
|
+
/** Initializes the router by reading the context and registering events if necessary. */
|
|
969
1125
|
async function initializeRouter() {
|
|
970
1126
|
const context = getRouterContext();
|
|
971
1127
|
if (isBackForwardNavigation()) handleBackForwardNavigation();
|
|
@@ -983,6 +1139,7 @@ async function initializeRouter() {
|
|
|
983
1139
|
await runHooks("ready", {}, context);
|
|
984
1140
|
return context;
|
|
985
1141
|
}
|
|
1142
|
+
/** Performs a local navigation to the given component without a round-trip. */
|
|
986
1143
|
async function performLocalNavigation(targetUrl, options) {
|
|
987
1144
|
const context = getRouterContext();
|
|
988
1145
|
const url = normalizeUrl(targetUrl);
|
|
@@ -1001,7 +1158,14 @@ async function performLocalNavigation(targetUrl, options) {
|
|
|
1001
1158
|
}
|
|
1002
1159
|
});
|
|
1003
1160
|
}
|
|
1161
|
+
//#endregion
|
|
1162
|
+
//#region src/authorization.ts
|
|
1163
|
+
/**
|
|
1164
|
+
* Checks whether the given data has the authorization for the given action.
|
|
1165
|
+
* If the data object has no authorization definition corresponding to the given action, this method will return `false`.
|
|
1166
|
+
*/
|
|
1004
1167
|
function can(resource, action) {
|
|
1005
1168
|
return resource.authorization?.[action] ?? false;
|
|
1006
1169
|
}
|
|
1170
|
+
//#endregion
|
|
1007
1171
|
export { can, constants_exports as constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridly/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.10.0-beta.
|
|
4
|
+
"version": "0.10.0-beta.13",
|
|
5
5
|
"description": "Core functionality of Hybridly",
|
|
6
6
|
"author": "Enzo Innocenzi <enzo@innocenzi.dev>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"axios": "^1.7.2"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@hybridly/utils": "0.10.0-beta.
|
|
46
|
+
"@hybridly/utils": "0.10.0-beta.13",
|
|
47
47
|
"qs": "^6.14.0",
|
|
48
48
|
"superjson": "^2.2.2"
|
|
49
49
|
},
|