@hybridly/core 0.4.0 → 0.4.2
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.cjs +200 -76
- package/dist/index.d.ts +14 -6
- package/dist/index.mjs +202 -77
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -160,7 +160,7 @@ async function restoreScrollPositions() {
|
|
|
160
160
|
utils.debug.scroll("No region found to restore.");
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
|
-
context.adapter.
|
|
163
|
+
context.adapter.executeOnMounted(() => {
|
|
164
164
|
utils.debug.scroll(`Restoring ${regions.length}/${context.scrollRegions.length} region(s).`);
|
|
165
165
|
regions.forEach((el, i) => el.scrollTo({
|
|
166
166
|
top: context.scrollRegions.at(i)?.top ?? el.scrollTop,
|
|
@@ -360,10 +360,59 @@ function createSerializer(options) {
|
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
function getUrlRegexForRoute(name) {
|
|
364
|
+
const routing = getRouting();
|
|
365
|
+
const definition = getRouteDefinition(name);
|
|
366
|
+
const path = definition.uri.replaceAll("/", "\\/");
|
|
367
|
+
const domain = definition.domain;
|
|
368
|
+
const protocolPrefix = routing.url.match(/^\w+:\/\//)?.[0];
|
|
369
|
+
const origin = domain ? `${protocolPrefix}${domain}${routing.port ? `:${routing.port}` : ""}`.replaceAll("/", "\\/") : routing.url.replaceAll("/", "\\/");
|
|
370
|
+
const urlPathRegexPattern = path.length > 0 ? `\\/${path.replace(/\/$/g, "")}` : "";
|
|
371
|
+
let urlRegexPattern = `^${origin.replaceAll(".", "\\.")}${urlPathRegexPattern}\\/?(\\?.*)?$`;
|
|
372
|
+
urlRegexPattern = urlRegexPattern.replace(/(\\\/?){([^}?]+)(\??)}/g, (_, slash, parameterName, optional) => {
|
|
373
|
+
const where = definition.wheres?.[parameterName];
|
|
374
|
+
let regexTemplate = where?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+";
|
|
375
|
+
regexTemplate = `(?<${parameterName}>${regexTemplate})`;
|
|
376
|
+
if (optional) {
|
|
377
|
+
return `(${slash ? "\\/?" : ""}${regexTemplate})?`;
|
|
378
|
+
}
|
|
379
|
+
return (slash ? "\\/" : "") + regexTemplate;
|
|
380
|
+
});
|
|
381
|
+
return RegExp(urlRegexPattern);
|
|
382
|
+
}
|
|
383
|
+
function urlMatchesRoute(url, name, routeParameters) {
|
|
384
|
+
const parameters = routeParameters || {};
|
|
385
|
+
const definition = getRouting().routes[name];
|
|
386
|
+
if (!definition) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
const matches = getUrlRegexForRoute(name).exec(url);
|
|
390
|
+
if (!matches) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
for (const k in matches.groups) {
|
|
394
|
+
matches.groups[k] = typeof matches.groups[k] === "string" ? decodeURIComponent(matches.groups[k]) : matches.groups[k];
|
|
395
|
+
}
|
|
396
|
+
return Object.keys(parameters).every((parameterName) => {
|
|
397
|
+
let value = parameters[parameterName];
|
|
398
|
+
const bindingProperty = definition.bindings?.[parameterName];
|
|
399
|
+
if (bindingProperty && typeof value === "object") {
|
|
400
|
+
value = value[bindingProperty];
|
|
401
|
+
}
|
|
402
|
+
return matches.groups?.[parameterName] === value.toString();
|
|
403
|
+
});
|
|
404
|
+
}
|
|
363
405
|
function generateRouteFromName(name, parameters, absolute, shouldThrow) {
|
|
364
406
|
const url = getUrlFromName(name, parameters, shouldThrow);
|
|
365
407
|
return absolute === false ? url.toString().replace(url.origin, "") : url.toString();
|
|
366
408
|
}
|
|
409
|
+
function getNameFromUrl(url, parameters) {
|
|
410
|
+
const routing = getRouting();
|
|
411
|
+
const routes = Object.values(routing.routes).map((x) => x.name);
|
|
412
|
+
return routes.find((routeName) => {
|
|
413
|
+
return urlMatchesRoute(url, routeName, parameters);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
367
416
|
function getUrlFromName(name, parameters, shouldThrow) {
|
|
368
417
|
const routing = getRouting();
|
|
369
418
|
const definition = getRouteDefinition(name);
|
|
@@ -376,32 +425,39 @@ function getUrlFromName(name, parameters, shouldThrow) {
|
|
|
376
425
|
}));
|
|
377
426
|
return url;
|
|
378
427
|
}
|
|
379
|
-
function
|
|
428
|
+
function getRouteParameterValue(routeName, parameterName, routeParameters) {
|
|
380
429
|
const routing = getRouting();
|
|
430
|
+
const definition = getRouteDefinition(routeName);
|
|
431
|
+
const parameters = routeParameters || {};
|
|
432
|
+
const value = (() => {
|
|
433
|
+
const value2 = parameters[parameterName];
|
|
434
|
+
const bindingProperty = definition.bindings?.[parameterName];
|
|
435
|
+
if (bindingProperty && value2 != null && typeof value2 === "object") {
|
|
436
|
+
return value2[bindingProperty];
|
|
437
|
+
}
|
|
438
|
+
return value2;
|
|
439
|
+
})();
|
|
440
|
+
if (value) {
|
|
441
|
+
const where = definition.wheres?.[parameterName];
|
|
442
|
+
if (where && !new RegExp(where).test(value)) {
|
|
443
|
+
console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
|
|
444
|
+
}
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
if (routing.defaults?.[parameterName]) {
|
|
448
|
+
return routing.defaults?.[parameterName];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
381
452
|
const definition = getRouteDefinition(routeName);
|
|
382
453
|
const parameters = routeParameters || {};
|
|
383
454
|
const missing = Object.keys(parameters);
|
|
384
|
-
const replaceParameter = (match, parameterName) => {
|
|
385
|
-
const
|
|
386
|
-
const value = (() => {
|
|
387
|
-
const value2 = parameters[parameterName];
|
|
388
|
-
const bindingProperty = definition.bindings?.[parameterName];
|
|
389
|
-
if (bindingProperty && typeof value2 === "object") {
|
|
390
|
-
return value2[bindingProperty];
|
|
391
|
-
}
|
|
392
|
-
return value2;
|
|
393
|
-
})();
|
|
455
|
+
const replaceParameter = (match, parameterName, optional) => {
|
|
456
|
+
const value = getRouteParameterValue(routeName, parameterName, parameters);
|
|
394
457
|
missing.splice(missing.indexOf(parameterName), 1);
|
|
395
458
|
if (value) {
|
|
396
|
-
const where = definition.wheres?.[parameterName];
|
|
397
|
-
if (where && !new RegExp(where).test(value)) {
|
|
398
|
-
console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
|
|
399
|
-
}
|
|
400
459
|
return value;
|
|
401
460
|
}
|
|
402
|
-
if (routing.defaults?.[parameterName]) {
|
|
403
|
-
return routing.defaults?.[parameterName];
|
|
404
|
-
}
|
|
405
461
|
if (optional) {
|
|
406
462
|
return "";
|
|
407
463
|
}
|
|
@@ -410,8 +466,8 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
|
410
466
|
}
|
|
411
467
|
throw new MissingRouteParameter(parameterName, routeName);
|
|
412
468
|
};
|
|
413
|
-
const path = definition.uri.replace(/{([^}?]+)\??}/g, replaceParameter);
|
|
414
|
-
const domain = definition.domain?.replace(/{([^}?]+)\??}/g, replaceParameter);
|
|
469
|
+
const path = definition.uri.replace(/{([^}?]+)(\??)}/g, replaceParameter);
|
|
470
|
+
const domain = definition.domain?.replace(/{([^}?]+)(\??)}/g, replaceParameter);
|
|
415
471
|
const remaining = Object.keys(parameters).filter((key) => missing.includes(key)).reduce((obj, key) => ({
|
|
416
472
|
...obj,
|
|
417
473
|
[key]: parameters[key]
|
|
@@ -441,30 +497,28 @@ function getRouting() {
|
|
|
441
497
|
}
|
|
442
498
|
return routing;
|
|
443
499
|
}
|
|
444
|
-
|
|
445
|
-
function isCurrentFromName(name, parameters, mode = "loose") {
|
|
446
|
-
const location = window.location;
|
|
447
|
-
const matchee = (() => {
|
|
448
|
-
try {
|
|
449
|
-
return makeUrl(generateRouteFromName(name, parameters, true, false));
|
|
450
|
-
} catch (error) {
|
|
451
|
-
}
|
|
452
|
-
})();
|
|
453
|
-
if (!matchee) {
|
|
454
|
-
return false;
|
|
455
|
-
}
|
|
456
|
-
if (mode === "strict") {
|
|
457
|
-
return location.href === matchee.href;
|
|
458
|
-
}
|
|
459
|
-
return location.href.startsWith(matchee.href);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
500
|
function route(name, parameters, absolute) {
|
|
463
501
|
return generateRouteFromName(name, parameters, absolute);
|
|
464
502
|
}
|
|
465
|
-
|
|
466
|
-
|
|
503
|
+
|
|
504
|
+
function getCurrentUrl() {
|
|
505
|
+
if (typeof window === "undefined") {
|
|
506
|
+
return getInternalRouterContext().url;
|
|
507
|
+
}
|
|
508
|
+
return window.location.toString();
|
|
509
|
+
}
|
|
510
|
+
function currentRouteMatches(name, parameters) {
|
|
511
|
+
const namePattern = name.replaceAll(".", "\\.").replaceAll("*", ".*");
|
|
512
|
+
const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
|
|
513
|
+
const currentUrl = getCurrentUrl();
|
|
514
|
+
return possibleRoutes.some((routeName) => {
|
|
515
|
+
return urlMatchesRoute(currentUrl, routeName, parameters);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
function getCurrentRouteName() {
|
|
519
|
+
return getNameFromUrl(getCurrentUrl());
|
|
467
520
|
}
|
|
521
|
+
|
|
468
522
|
function updateRoutingConfiguration(routing) {
|
|
469
523
|
if (!routing) {
|
|
470
524
|
return;
|
|
@@ -500,6 +554,7 @@ async function initializeContext(options) {
|
|
|
500
554
|
plugins: options.plugins ?? [],
|
|
501
555
|
axios: options.axios ?? axios__default.create(),
|
|
502
556
|
routing: options.routing,
|
|
557
|
+
preloadCache: /* @__PURE__ */ new Map(),
|
|
503
558
|
hooks: {},
|
|
504
559
|
memo: {}
|
|
505
560
|
};
|
|
@@ -590,6 +645,52 @@ async function closeDialog(options) {
|
|
|
590
645
|
});
|
|
591
646
|
}
|
|
592
647
|
|
|
648
|
+
function isPreloaded(targetUrl) {
|
|
649
|
+
const context = getInternalRouterContext();
|
|
650
|
+
return context.preloadCache.has(targetUrl.toString()) ?? false;
|
|
651
|
+
}
|
|
652
|
+
function getPreloadedRequest(targetUrl) {
|
|
653
|
+
const context = getInternalRouterContext();
|
|
654
|
+
return context.preloadCache.get(targetUrl.toString());
|
|
655
|
+
}
|
|
656
|
+
function storePreloadRequest(targetUrl, response) {
|
|
657
|
+
const context = getInternalRouterContext();
|
|
658
|
+
context.preloadCache.set(targetUrl.toString(), response);
|
|
659
|
+
}
|
|
660
|
+
function discardPreloadedRequest(targetUrl) {
|
|
661
|
+
const context = getInternalRouterContext();
|
|
662
|
+
return context.preloadCache.delete(targetUrl.toString());
|
|
663
|
+
}
|
|
664
|
+
async function performPreloadRequest(options) {
|
|
665
|
+
const context = getRouterContext();
|
|
666
|
+
const url = makeUrl(options.url ?? context.url);
|
|
667
|
+
if (isPreloaded(url)) {
|
|
668
|
+
utils.debug.router("This request is already preloaded.");
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
if (context.pendingNavigation) {
|
|
672
|
+
utils.debug.router("A navigation is pending, preload aborted.");
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
if (options.method !== "GET") {
|
|
676
|
+
utils.debug.router("Cannot preload non-GET requests.");
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
utils.debug.router(`Preloading response for [${url.toString()}]`);
|
|
680
|
+
try {
|
|
681
|
+
const response = await performHybridRequest(url, options);
|
|
682
|
+
if (!isHybridResponse(response)) {
|
|
683
|
+
utils.debug.router("Preload result was invalid.");
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
storePreloadRequest(url, response);
|
|
687
|
+
return true;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
utils.debug.router("Preloading failed.");
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
593
694
|
const router = {
|
|
594
695
|
abort: async () => getRouterContext().pendingNavigation?.controller.abort(),
|
|
595
696
|
active: () => !!getRouterContext().pendingNavigation,
|
|
@@ -601,12 +702,15 @@ const router = {
|
|
|
601
702
|
patch: async (url, options = {}) => await performHybridNavigation({ preserveState: true, ...options, url, method: "PATCH" }),
|
|
602
703
|
delete: async (url, options = {}) => await performHybridNavigation({ preserveState: true, ...options, url, method: "DELETE" }),
|
|
603
704
|
local: async (url, options = {}) => await performLocalNavigation(url, options),
|
|
705
|
+
preload: async (url, options = {}) => await performPreloadRequest({ ...options, url, method: "GET" }),
|
|
604
706
|
external: (url, data = {}) => navigateToExternalUrl(url, data),
|
|
605
707
|
to: async (name, parameters, options) => {
|
|
606
708
|
const url = generateRouteFromName(name, parameters);
|
|
607
709
|
const method = getRouteDefinition(name).method.at(0);
|
|
608
710
|
return await performHybridNavigation({ url, ...options, method });
|
|
609
711
|
},
|
|
712
|
+
matches: (name, parameters) => currentRouteMatches(name, parameters),
|
|
713
|
+
current: () => getCurrentRouteName(),
|
|
610
714
|
dialog: {
|
|
611
715
|
close: (options) => closeDialog(options)
|
|
612
716
|
},
|
|
@@ -632,11 +736,6 @@ async function performHybridNavigation(options) {
|
|
|
632
736
|
if ((utils.hasFiles(options.data) || options.useFormData) && !(options.data instanceof FormData)) {
|
|
633
737
|
options.data = utils.objectToFormData(options.data);
|
|
634
738
|
utils.debug.router("Converted data to FormData.", options.data);
|
|
635
|
-
if (options.method && ["PUT", "PATCH", "DELETE"].includes(options.method) && options.spoof !== false) {
|
|
636
|
-
utils.debug.router(`Automatically spoofing method ${options.method}.`);
|
|
637
|
-
options.data.append("_method", options.method);
|
|
638
|
-
options.method = "POST";
|
|
639
|
-
}
|
|
640
739
|
}
|
|
641
740
|
if (!(options.data instanceof FormData) && options.method === "GET" && Object.keys(options.data ?? {}).length) {
|
|
642
741
|
utils.debug.router("Transforming data to query parameters.", options.data);
|
|
@@ -645,6 +744,19 @@ async function performHybridNavigation(options) {
|
|
|
645
744
|
});
|
|
646
745
|
options.data = {};
|
|
647
746
|
}
|
|
747
|
+
if (["PUT", "PATCH", "DELETE"].includes(options.method) && options.spoof !== false) {
|
|
748
|
+
utils.debug.router(`Automatically spoofing method ${options.method}.`);
|
|
749
|
+
if (options.data instanceof FormData) {
|
|
750
|
+
options.data.append("_method", options.method);
|
|
751
|
+
} else if (Object.keys(options.data ?? {}).length) {
|
|
752
|
+
Object.assign(options.data, { _method: options.method });
|
|
753
|
+
} else if (typeof options.data === "undefined") {
|
|
754
|
+
options.data = { _method: options.method };
|
|
755
|
+
} else {
|
|
756
|
+
utils.debug.router("Could not spoof method because body type is not supported.", options.data);
|
|
757
|
+
}
|
|
758
|
+
options.method = "POST";
|
|
759
|
+
}
|
|
648
760
|
if (!await runHooks("before", options.hooks, options, context)) {
|
|
649
761
|
utils.debug.router('"before" event returned false, aborting the navigation.');
|
|
650
762
|
throw new NavigationCancelledError('The navigation was cancelled by the "before" event.');
|
|
@@ -670,35 +782,7 @@ async function performHybridNavigation(options) {
|
|
|
670
782
|
});
|
|
671
783
|
await runHooks("start", options.hooks, context);
|
|
672
784
|
utils.debug.router("Making request with axios.");
|
|
673
|
-
const response = await
|
|
674
|
-
url: targetUrl.toString(),
|
|
675
|
-
method: options.method,
|
|
676
|
-
data: options.method === "GET" ? {} : options.data,
|
|
677
|
-
params: options.method === "GET" ? options.data : {},
|
|
678
|
-
signal: abortController.signal,
|
|
679
|
-
headers: {
|
|
680
|
-
...options.headers,
|
|
681
|
-
...context.dialog ? { [DIALOG_KEY_HEADER]: context.dialog.key } : {},
|
|
682
|
-
...context.dialog ? { [DIALOG_REDIRECT_HEADER]: context.dialog.redirectUrl ?? "" } : {},
|
|
683
|
-
...utils.when(options.only !== void 0 || options.except !== void 0, {
|
|
684
|
-
[PARTIAL_COMPONENT_HEADER]: context.view.component,
|
|
685
|
-
...utils.when(options.only, { [ONLY_DATA_HEADER]: JSON.stringify(options.only) }, {}),
|
|
686
|
-
...utils.when(options.except, { [EXCEPT_DATA_HEADER]: JSON.stringify(options.except) }, {})
|
|
687
|
-
}, {}),
|
|
688
|
-
...utils.when(options.errorBag, { [ERROR_BAG_HEADER]: options.errorBag }, {}),
|
|
689
|
-
...utils.when(context.version, { [VERSION_HEADER]: context.version }, {}),
|
|
690
|
-
[HYBRIDLY_HEADER]: true,
|
|
691
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
692
|
-
"Accept": "text/html, application/xhtml+xml"
|
|
693
|
-
},
|
|
694
|
-
validateStatus: () => true,
|
|
695
|
-
onUploadProgress: async (event) => {
|
|
696
|
-
await runHooks("progress", options.hooks, {
|
|
697
|
-
event,
|
|
698
|
-
percentage: Math.round(event.loaded / (event.total ?? 0) * 100)
|
|
699
|
-
}, context);
|
|
700
|
-
}
|
|
701
|
-
});
|
|
785
|
+
const response = await performHybridRequest(targetUrl, options, abortController);
|
|
702
786
|
await runHooks("data", options.hooks, response, context);
|
|
703
787
|
if (isExternalResponse(response)) {
|
|
704
788
|
utils.debug.router("The response is explicitely external.");
|
|
@@ -842,6 +926,47 @@ async function navigate(options) {
|
|
|
842
926
|
resetScrollPositions();
|
|
843
927
|
}
|
|
844
928
|
await runHooks("navigated", {}, options, context);
|
|
929
|
+
context.adapter.executeOnMounted(() => {
|
|
930
|
+
runHooks("mounted", {}, context);
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
async function performHybridRequest(targetUrl, options, abortController) {
|
|
934
|
+
const context = getInternalRouterContext();
|
|
935
|
+
const preloaded = getPreloadedRequest(targetUrl);
|
|
936
|
+
if (preloaded) {
|
|
937
|
+
utils.debug.router(`Found a pre-loaded request for [${targetUrl}]`);
|
|
938
|
+
discardPreloadedRequest(targetUrl);
|
|
939
|
+
return preloaded;
|
|
940
|
+
}
|
|
941
|
+
return await context.axios.request({
|
|
942
|
+
url: targetUrl.toString(),
|
|
943
|
+
method: options.method,
|
|
944
|
+
data: options.method === "GET" ? {} : options.data,
|
|
945
|
+
params: options.method === "GET" ? options.data : {},
|
|
946
|
+
signal: abortController?.signal,
|
|
947
|
+
headers: {
|
|
948
|
+
...options.headers,
|
|
949
|
+
...context.dialog ? { [DIALOG_KEY_HEADER]: context.dialog.key } : {},
|
|
950
|
+
...context.dialog ? { [DIALOG_REDIRECT_HEADER]: context.dialog.redirectUrl ?? "" } : {},
|
|
951
|
+
...utils.when(options.only !== void 0 || options.except !== void 0, {
|
|
952
|
+
[PARTIAL_COMPONENT_HEADER]: context.view.component,
|
|
953
|
+
...utils.when(options.only, { [ONLY_DATA_HEADER]: JSON.stringify(options.only) }, {}),
|
|
954
|
+
...utils.when(options.except, { [EXCEPT_DATA_HEADER]: JSON.stringify(options.except) }, {})
|
|
955
|
+
}, {}),
|
|
956
|
+
...utils.when(options.errorBag, { [ERROR_BAG_HEADER]: options.errorBag }, {}),
|
|
957
|
+
...utils.when(context.version, { [VERSION_HEADER]: context.version }, {}),
|
|
958
|
+
[HYBRIDLY_HEADER]: true,
|
|
959
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
960
|
+
"Accept": "text/html, application/xhtml+xml"
|
|
961
|
+
},
|
|
962
|
+
validateStatus: () => true,
|
|
963
|
+
onUploadProgress: async (event) => {
|
|
964
|
+
await runHooks("progress", options.hooks, {
|
|
965
|
+
event,
|
|
966
|
+
percentage: Math.round(event.loaded / (event.total ?? 0) * 100)
|
|
967
|
+
}, context);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
845
970
|
}
|
|
846
971
|
async function initializeRouter() {
|
|
847
972
|
const context = getRouterContext();
|
|
@@ -887,7 +1012,6 @@ function can(resource, action) {
|
|
|
887
1012
|
exports.can = can;
|
|
888
1013
|
exports.constants = constants;
|
|
889
1014
|
exports.createRouter = createRouter;
|
|
890
|
-
exports.current = current;
|
|
891
1015
|
exports.definePlugin = definePlugin;
|
|
892
1016
|
exports.getRouterContext = getRouterContext;
|
|
893
1017
|
exports.makeUrl = makeUrl;
|
package/dist/index.d.ts
CHANGED
|
@@ -70,6 +70,10 @@ interface Hooks extends RequestHooks {
|
|
|
70
70
|
* Called when a component has been navigated to.
|
|
71
71
|
*/
|
|
72
72
|
navigated: (options: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
73
|
+
/**
|
|
74
|
+
* Called when a component has been navigated to and was mounted by the adapter.
|
|
75
|
+
*/
|
|
76
|
+
mounted: (context: InternalRouterContext) => MaybePromise<any>;
|
|
73
77
|
}
|
|
74
78
|
interface HookOptions {
|
|
75
79
|
/** Executes the hook only once. */
|
|
@@ -228,6 +232,12 @@ interface Router {
|
|
|
228
232
|
external: (url: UrlResolvable, data?: HybridRequestOptions['data']) => void;
|
|
229
233
|
/** Navigates to the given URL without a server round-trip. */
|
|
230
234
|
local: (url: UrlResolvable, options: ComponentNavigationOptions) => Promise<void>;
|
|
235
|
+
/** Preloads the given URL. The next time this URL is navigated to, it will be loaded from the cache. */
|
|
236
|
+
preload: (url: UrlResolvable, options?: Omit<HybridRequestOptions, 'method' | 'url'>) => Promise<boolean>;
|
|
237
|
+
/** Determines if the given route name and parameters matches the current route. */
|
|
238
|
+
matches: <T extends RouteName>(name: T, parameters?: RouteParameters<T>) => boolean;
|
|
239
|
+
/** Gets the current route name. Returns `undefined` is unknown. */
|
|
240
|
+
current: () => string | undefined;
|
|
231
241
|
/** Access the dialog router. */
|
|
232
242
|
dialog: DialogRouter;
|
|
233
243
|
/** Access the history state. */
|
|
@@ -368,6 +378,8 @@ interface InternalRouterContext {
|
|
|
368
378
|
routing?: RoutingConfiguration;
|
|
369
379
|
/** Whether to display response error modals. */
|
|
370
380
|
responseErrorModals?: boolean;
|
|
381
|
+
/** Cache of preload requests. */
|
|
382
|
+
preloadCache: Map<string, AxiosResponse>;
|
|
371
383
|
}
|
|
372
384
|
/** Router context. */
|
|
373
385
|
type RouterContext = Readonly<InternalRouterContext>;
|
|
@@ -382,7 +394,7 @@ interface Adapter {
|
|
|
382
394
|
/** Called when a dialog is closed. */
|
|
383
395
|
onDialogClose?: (context: InternalRouterContext) => void;
|
|
384
396
|
/** Called when Hybridly is waiting for a component to be mounted. The given callback should be executed after the view component is mounted. */
|
|
385
|
-
|
|
397
|
+
executeOnMounted: (callback: Function) => void;
|
|
386
398
|
}
|
|
387
399
|
interface ResolvedAdapter extends Adapter {
|
|
388
400
|
updateRoutingConfiguration: (routing?: RoutingConfiguration) => void;
|
|
@@ -426,10 +438,6 @@ declare function can<Authorizations extends Record<string, boolean>, Data extend
|
|
|
426
438
|
* Generates a route from the given route name.
|
|
427
439
|
*/
|
|
428
440
|
declare function route<T extends RouteName>(name: T, parameters?: RouteParameters<T>, absolute?: boolean): string;
|
|
429
|
-
/**
|
|
430
|
-
* Determines if the current route correspond to the given route name and parameters.
|
|
431
|
-
*/
|
|
432
|
-
declare function current<T extends RouteName>(name: T, parameters?: RouteParameters<T>, mode?: 'loose' | 'strict'): boolean;
|
|
433
441
|
|
|
434
442
|
interface DynamicConfiguration {
|
|
435
443
|
architecture: {
|
|
@@ -492,4 +500,4 @@ declare namespace constants {
|
|
|
492
500
|
};
|
|
493
501
|
}
|
|
494
502
|
|
|
495
|
-
export { Authorizable, DynamicConfiguration, GlobalRouteCollection, HybridPayload, HybridRequestOptions, MaybePromise, Method, NavigationResponse, Plugin, Progress, ResolveComponent, RouteDefinition, RouteName, RouteParameters, Router, RouterContext, RouterContextOptions, RoutingConfiguration, UrlResolvable, can, constants, createRouter,
|
|
503
|
+
export { Authorizable, DynamicConfiguration, GlobalRouteCollection, HybridPayload, HybridRequestOptions, MaybePromise, Method, NavigationResponse, Plugin, Progress, ResolveComponent, RouteDefinition, RouteName, RouteParameters, Router, RouterContext, RouterContextOptions, RoutingConfiguration, UrlResolvable, can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { debug, merge, removeTrailingSlash, debounce, random, hasFiles, objectToFormData,
|
|
1
|
+
import { debug, merge, removeTrailingSlash, debounce, random, hasFiles, objectToFormData, match, showResponseErrorModal, when } from '@hybridly/utils';
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
import { stringify, parse } from 'superjson';
|
|
4
4
|
import qs from 'qs';
|
|
@@ -151,7 +151,7 @@ async function restoreScrollPositions() {
|
|
|
151
151
|
debug.scroll("No region found to restore.");
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
|
-
context.adapter.
|
|
154
|
+
context.adapter.executeOnMounted(() => {
|
|
155
155
|
debug.scroll(`Restoring ${regions.length}/${context.scrollRegions.length} region(s).`);
|
|
156
156
|
regions.forEach((el, i) => el.scrollTo({
|
|
157
157
|
top: context.scrollRegions.at(i)?.top ?? el.scrollTop,
|
|
@@ -351,10 +351,59 @@ function createSerializer(options) {
|
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
function getUrlRegexForRoute(name) {
|
|
355
|
+
const routing = getRouting();
|
|
356
|
+
const definition = getRouteDefinition(name);
|
|
357
|
+
const path = definition.uri.replaceAll("/", "\\/");
|
|
358
|
+
const domain = definition.domain;
|
|
359
|
+
const protocolPrefix = routing.url.match(/^\w+:\/\//)?.[0];
|
|
360
|
+
const origin = domain ? `${protocolPrefix}${domain}${routing.port ? `:${routing.port}` : ""}`.replaceAll("/", "\\/") : routing.url.replaceAll("/", "\\/");
|
|
361
|
+
const urlPathRegexPattern = path.length > 0 ? `\\/${path.replace(/\/$/g, "")}` : "";
|
|
362
|
+
let urlRegexPattern = `^${origin.replaceAll(".", "\\.")}${urlPathRegexPattern}\\/?(\\?.*)?$`;
|
|
363
|
+
urlRegexPattern = urlRegexPattern.replace(/(\\\/?){([^}?]+)(\??)}/g, (_, slash, parameterName, optional) => {
|
|
364
|
+
const where = definition.wheres?.[parameterName];
|
|
365
|
+
let regexTemplate = where?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+";
|
|
366
|
+
regexTemplate = `(?<${parameterName}>${regexTemplate})`;
|
|
367
|
+
if (optional) {
|
|
368
|
+
return `(${slash ? "\\/?" : ""}${regexTemplate})?`;
|
|
369
|
+
}
|
|
370
|
+
return (slash ? "\\/" : "") + regexTemplate;
|
|
371
|
+
});
|
|
372
|
+
return RegExp(urlRegexPattern);
|
|
373
|
+
}
|
|
374
|
+
function urlMatchesRoute(url, name, routeParameters) {
|
|
375
|
+
const parameters = routeParameters || {};
|
|
376
|
+
const definition = getRouting().routes[name];
|
|
377
|
+
if (!definition) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
const matches = getUrlRegexForRoute(name).exec(url);
|
|
381
|
+
if (!matches) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
for (const k in matches.groups) {
|
|
385
|
+
matches.groups[k] = typeof matches.groups[k] === "string" ? decodeURIComponent(matches.groups[k]) : matches.groups[k];
|
|
386
|
+
}
|
|
387
|
+
return Object.keys(parameters).every((parameterName) => {
|
|
388
|
+
let value = parameters[parameterName];
|
|
389
|
+
const bindingProperty = definition.bindings?.[parameterName];
|
|
390
|
+
if (bindingProperty && typeof value === "object") {
|
|
391
|
+
value = value[bindingProperty];
|
|
392
|
+
}
|
|
393
|
+
return matches.groups?.[parameterName] === value.toString();
|
|
394
|
+
});
|
|
395
|
+
}
|
|
354
396
|
function generateRouteFromName(name, parameters, absolute, shouldThrow) {
|
|
355
397
|
const url = getUrlFromName(name, parameters, shouldThrow);
|
|
356
398
|
return absolute === false ? url.toString().replace(url.origin, "") : url.toString();
|
|
357
399
|
}
|
|
400
|
+
function getNameFromUrl(url, parameters) {
|
|
401
|
+
const routing = getRouting();
|
|
402
|
+
const routes = Object.values(routing.routes).map((x) => x.name);
|
|
403
|
+
return routes.find((routeName) => {
|
|
404
|
+
return urlMatchesRoute(url, routeName, parameters);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
358
407
|
function getUrlFromName(name, parameters, shouldThrow) {
|
|
359
408
|
const routing = getRouting();
|
|
360
409
|
const definition = getRouteDefinition(name);
|
|
@@ -367,32 +416,39 @@ function getUrlFromName(name, parameters, shouldThrow) {
|
|
|
367
416
|
}));
|
|
368
417
|
return url;
|
|
369
418
|
}
|
|
370
|
-
function
|
|
419
|
+
function getRouteParameterValue(routeName, parameterName, routeParameters) {
|
|
371
420
|
const routing = getRouting();
|
|
421
|
+
const definition = getRouteDefinition(routeName);
|
|
422
|
+
const parameters = routeParameters || {};
|
|
423
|
+
const value = (() => {
|
|
424
|
+
const value2 = parameters[parameterName];
|
|
425
|
+
const bindingProperty = definition.bindings?.[parameterName];
|
|
426
|
+
if (bindingProperty && value2 != null && typeof value2 === "object") {
|
|
427
|
+
return value2[bindingProperty];
|
|
428
|
+
}
|
|
429
|
+
return value2;
|
|
430
|
+
})();
|
|
431
|
+
if (value) {
|
|
432
|
+
const where = definition.wheres?.[parameterName];
|
|
433
|
+
if (where && !new RegExp(where).test(value)) {
|
|
434
|
+
console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
|
|
435
|
+
}
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
if (routing.defaults?.[parameterName]) {
|
|
439
|
+
return routing.defaults?.[parameterName];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
372
443
|
const definition = getRouteDefinition(routeName);
|
|
373
444
|
const parameters = routeParameters || {};
|
|
374
445
|
const missing = Object.keys(parameters);
|
|
375
|
-
const replaceParameter = (match, parameterName) => {
|
|
376
|
-
const
|
|
377
|
-
const value = (() => {
|
|
378
|
-
const value2 = parameters[parameterName];
|
|
379
|
-
const bindingProperty = definition.bindings?.[parameterName];
|
|
380
|
-
if (bindingProperty && typeof value2 === "object") {
|
|
381
|
-
return value2[bindingProperty];
|
|
382
|
-
}
|
|
383
|
-
return value2;
|
|
384
|
-
})();
|
|
446
|
+
const replaceParameter = (match, parameterName, optional) => {
|
|
447
|
+
const value = getRouteParameterValue(routeName, parameterName, parameters);
|
|
385
448
|
missing.splice(missing.indexOf(parameterName), 1);
|
|
386
449
|
if (value) {
|
|
387
|
-
const where = definition.wheres?.[parameterName];
|
|
388
|
-
if (where && !new RegExp(where).test(value)) {
|
|
389
|
-
console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
|
|
390
|
-
}
|
|
391
450
|
return value;
|
|
392
451
|
}
|
|
393
|
-
if (routing.defaults?.[parameterName]) {
|
|
394
|
-
return routing.defaults?.[parameterName];
|
|
395
|
-
}
|
|
396
452
|
if (optional) {
|
|
397
453
|
return "";
|
|
398
454
|
}
|
|
@@ -401,8 +457,8 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
|
|
|
401
457
|
}
|
|
402
458
|
throw new MissingRouteParameter(parameterName, routeName);
|
|
403
459
|
};
|
|
404
|
-
const path = definition.uri.replace(/{([^}?]+)\??}/g, replaceParameter);
|
|
405
|
-
const domain = definition.domain?.replace(/{([^}?]+)\??}/g, replaceParameter);
|
|
460
|
+
const path = definition.uri.replace(/{([^}?]+)(\??)}/g, replaceParameter);
|
|
461
|
+
const domain = definition.domain?.replace(/{([^}?]+)(\??)}/g, replaceParameter);
|
|
406
462
|
const remaining = Object.keys(parameters).filter((key) => missing.includes(key)).reduce((obj, key) => ({
|
|
407
463
|
...obj,
|
|
408
464
|
[key]: parameters[key]
|
|
@@ -432,30 +488,28 @@ function getRouting() {
|
|
|
432
488
|
}
|
|
433
489
|
return routing;
|
|
434
490
|
}
|
|
435
|
-
|
|
436
|
-
function isCurrentFromName(name, parameters, mode = "loose") {
|
|
437
|
-
const location = window.location;
|
|
438
|
-
const matchee = (() => {
|
|
439
|
-
try {
|
|
440
|
-
return makeUrl(generateRouteFromName(name, parameters, true, false));
|
|
441
|
-
} catch (error) {
|
|
442
|
-
}
|
|
443
|
-
})();
|
|
444
|
-
if (!matchee) {
|
|
445
|
-
return false;
|
|
446
|
-
}
|
|
447
|
-
if (mode === "strict") {
|
|
448
|
-
return location.href === matchee.href;
|
|
449
|
-
}
|
|
450
|
-
return location.href.startsWith(matchee.href);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
491
|
function route(name, parameters, absolute) {
|
|
454
492
|
return generateRouteFromName(name, parameters, absolute);
|
|
455
493
|
}
|
|
456
|
-
|
|
457
|
-
|
|
494
|
+
|
|
495
|
+
function getCurrentUrl() {
|
|
496
|
+
if (typeof window === "undefined") {
|
|
497
|
+
return getInternalRouterContext().url;
|
|
498
|
+
}
|
|
499
|
+
return window.location.toString();
|
|
500
|
+
}
|
|
501
|
+
function currentRouteMatches(name, parameters) {
|
|
502
|
+
const namePattern = name.replaceAll(".", "\\.").replaceAll("*", ".*");
|
|
503
|
+
const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
|
|
504
|
+
const currentUrl = getCurrentUrl();
|
|
505
|
+
return possibleRoutes.some((routeName) => {
|
|
506
|
+
return urlMatchesRoute(currentUrl, routeName, parameters);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function getCurrentRouteName() {
|
|
510
|
+
return getNameFromUrl(getCurrentUrl());
|
|
458
511
|
}
|
|
512
|
+
|
|
459
513
|
function updateRoutingConfiguration(routing) {
|
|
460
514
|
if (!routing) {
|
|
461
515
|
return;
|
|
@@ -491,6 +545,7 @@ async function initializeContext(options) {
|
|
|
491
545
|
plugins: options.plugins ?? [],
|
|
492
546
|
axios: options.axios ?? axios.create(),
|
|
493
547
|
routing: options.routing,
|
|
548
|
+
preloadCache: /* @__PURE__ */ new Map(),
|
|
494
549
|
hooks: {},
|
|
495
550
|
memo: {}
|
|
496
551
|
};
|
|
@@ -581,6 +636,52 @@ async function closeDialog(options) {
|
|
|
581
636
|
});
|
|
582
637
|
}
|
|
583
638
|
|
|
639
|
+
function isPreloaded(targetUrl) {
|
|
640
|
+
const context = getInternalRouterContext();
|
|
641
|
+
return context.preloadCache.has(targetUrl.toString()) ?? false;
|
|
642
|
+
}
|
|
643
|
+
function getPreloadedRequest(targetUrl) {
|
|
644
|
+
const context = getInternalRouterContext();
|
|
645
|
+
return context.preloadCache.get(targetUrl.toString());
|
|
646
|
+
}
|
|
647
|
+
function storePreloadRequest(targetUrl, response) {
|
|
648
|
+
const context = getInternalRouterContext();
|
|
649
|
+
context.preloadCache.set(targetUrl.toString(), response);
|
|
650
|
+
}
|
|
651
|
+
function discardPreloadedRequest(targetUrl) {
|
|
652
|
+
const context = getInternalRouterContext();
|
|
653
|
+
return context.preloadCache.delete(targetUrl.toString());
|
|
654
|
+
}
|
|
655
|
+
async function performPreloadRequest(options) {
|
|
656
|
+
const context = getRouterContext();
|
|
657
|
+
const url = makeUrl(options.url ?? context.url);
|
|
658
|
+
if (isPreloaded(url)) {
|
|
659
|
+
debug.router("This request is already preloaded.");
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
if (context.pendingNavigation) {
|
|
663
|
+
debug.router("A navigation is pending, preload aborted.");
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
if (options.method !== "GET") {
|
|
667
|
+
debug.router("Cannot preload non-GET requests.");
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
debug.router(`Preloading response for [${url.toString()}]`);
|
|
671
|
+
try {
|
|
672
|
+
const response = await performHybridRequest(url, options);
|
|
673
|
+
if (!isHybridResponse(response)) {
|
|
674
|
+
debug.router("Preload result was invalid.");
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
storePreloadRequest(url, response);
|
|
678
|
+
return true;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
debug.router("Preloading failed.");
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
584
685
|
const router = {
|
|
585
686
|
abort: async () => getRouterContext().pendingNavigation?.controller.abort(),
|
|
586
687
|
active: () => !!getRouterContext().pendingNavigation,
|
|
@@ -592,12 +693,15 @@ const router = {
|
|
|
592
693
|
patch: async (url, options = {}) => await performHybridNavigation({ preserveState: true, ...options, url, method: "PATCH" }),
|
|
593
694
|
delete: async (url, options = {}) => await performHybridNavigation({ preserveState: true, ...options, url, method: "DELETE" }),
|
|
594
695
|
local: async (url, options = {}) => await performLocalNavigation(url, options),
|
|
696
|
+
preload: async (url, options = {}) => await performPreloadRequest({ ...options, url, method: "GET" }),
|
|
595
697
|
external: (url, data = {}) => navigateToExternalUrl(url, data),
|
|
596
698
|
to: async (name, parameters, options) => {
|
|
597
699
|
const url = generateRouteFromName(name, parameters);
|
|
598
700
|
const method = getRouteDefinition(name).method.at(0);
|
|
599
701
|
return await performHybridNavigation({ url, ...options, method });
|
|
600
702
|
},
|
|
703
|
+
matches: (name, parameters) => currentRouteMatches(name, parameters),
|
|
704
|
+
current: () => getCurrentRouteName(),
|
|
601
705
|
dialog: {
|
|
602
706
|
close: (options) => closeDialog(options)
|
|
603
707
|
},
|
|
@@ -623,11 +727,6 @@ async function performHybridNavigation(options) {
|
|
|
623
727
|
if ((hasFiles(options.data) || options.useFormData) && !(options.data instanceof FormData)) {
|
|
624
728
|
options.data = objectToFormData(options.data);
|
|
625
729
|
debug.router("Converted data to FormData.", options.data);
|
|
626
|
-
if (options.method && ["PUT", "PATCH", "DELETE"].includes(options.method) && options.spoof !== false) {
|
|
627
|
-
debug.router(`Automatically spoofing method ${options.method}.`);
|
|
628
|
-
options.data.append("_method", options.method);
|
|
629
|
-
options.method = "POST";
|
|
630
|
-
}
|
|
631
730
|
}
|
|
632
731
|
if (!(options.data instanceof FormData) && options.method === "GET" && Object.keys(options.data ?? {}).length) {
|
|
633
732
|
debug.router("Transforming data to query parameters.", options.data);
|
|
@@ -636,6 +735,19 @@ async function performHybridNavigation(options) {
|
|
|
636
735
|
});
|
|
637
736
|
options.data = {};
|
|
638
737
|
}
|
|
738
|
+
if (["PUT", "PATCH", "DELETE"].includes(options.method) && options.spoof !== false) {
|
|
739
|
+
debug.router(`Automatically spoofing method ${options.method}.`);
|
|
740
|
+
if (options.data instanceof FormData) {
|
|
741
|
+
options.data.append("_method", options.method);
|
|
742
|
+
} else if (Object.keys(options.data ?? {}).length) {
|
|
743
|
+
Object.assign(options.data, { _method: options.method });
|
|
744
|
+
} else if (typeof options.data === "undefined") {
|
|
745
|
+
options.data = { _method: options.method };
|
|
746
|
+
} else {
|
|
747
|
+
debug.router("Could not spoof method because body type is not supported.", options.data);
|
|
748
|
+
}
|
|
749
|
+
options.method = "POST";
|
|
750
|
+
}
|
|
639
751
|
if (!await runHooks("before", options.hooks, options, context)) {
|
|
640
752
|
debug.router('"before" event returned false, aborting the navigation.');
|
|
641
753
|
throw new NavigationCancelledError('The navigation was cancelled by the "before" event.');
|
|
@@ -661,35 +773,7 @@ async function performHybridNavigation(options) {
|
|
|
661
773
|
});
|
|
662
774
|
await runHooks("start", options.hooks, context);
|
|
663
775
|
debug.router("Making request with axios.");
|
|
664
|
-
const response = await
|
|
665
|
-
url: targetUrl.toString(),
|
|
666
|
-
method: options.method,
|
|
667
|
-
data: options.method === "GET" ? {} : options.data,
|
|
668
|
-
params: options.method === "GET" ? options.data : {},
|
|
669
|
-
signal: abortController.signal,
|
|
670
|
-
headers: {
|
|
671
|
-
...options.headers,
|
|
672
|
-
...context.dialog ? { [DIALOG_KEY_HEADER]: context.dialog.key } : {},
|
|
673
|
-
...context.dialog ? { [DIALOG_REDIRECT_HEADER]: context.dialog.redirectUrl ?? "" } : {},
|
|
674
|
-
...when(options.only !== void 0 || options.except !== void 0, {
|
|
675
|
-
[PARTIAL_COMPONENT_HEADER]: context.view.component,
|
|
676
|
-
...when(options.only, { [ONLY_DATA_HEADER]: JSON.stringify(options.only) }, {}),
|
|
677
|
-
...when(options.except, { [EXCEPT_DATA_HEADER]: JSON.stringify(options.except) }, {})
|
|
678
|
-
}, {}),
|
|
679
|
-
...when(options.errorBag, { [ERROR_BAG_HEADER]: options.errorBag }, {}),
|
|
680
|
-
...when(context.version, { [VERSION_HEADER]: context.version }, {}),
|
|
681
|
-
[HYBRIDLY_HEADER]: true,
|
|
682
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
683
|
-
"Accept": "text/html, application/xhtml+xml"
|
|
684
|
-
},
|
|
685
|
-
validateStatus: () => true,
|
|
686
|
-
onUploadProgress: async (event) => {
|
|
687
|
-
await runHooks("progress", options.hooks, {
|
|
688
|
-
event,
|
|
689
|
-
percentage: Math.round(event.loaded / (event.total ?? 0) * 100)
|
|
690
|
-
}, context);
|
|
691
|
-
}
|
|
692
|
-
});
|
|
776
|
+
const response = await performHybridRequest(targetUrl, options, abortController);
|
|
693
777
|
await runHooks("data", options.hooks, response, context);
|
|
694
778
|
if (isExternalResponse(response)) {
|
|
695
779
|
debug.router("The response is explicitely external.");
|
|
@@ -833,6 +917,47 @@ async function navigate(options) {
|
|
|
833
917
|
resetScrollPositions();
|
|
834
918
|
}
|
|
835
919
|
await runHooks("navigated", {}, options, context);
|
|
920
|
+
context.adapter.executeOnMounted(() => {
|
|
921
|
+
runHooks("mounted", {}, context);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async function performHybridRequest(targetUrl, options, abortController) {
|
|
925
|
+
const context = getInternalRouterContext();
|
|
926
|
+
const preloaded = getPreloadedRequest(targetUrl);
|
|
927
|
+
if (preloaded) {
|
|
928
|
+
debug.router(`Found a pre-loaded request for [${targetUrl}]`);
|
|
929
|
+
discardPreloadedRequest(targetUrl);
|
|
930
|
+
return preloaded;
|
|
931
|
+
}
|
|
932
|
+
return await context.axios.request({
|
|
933
|
+
url: targetUrl.toString(),
|
|
934
|
+
method: options.method,
|
|
935
|
+
data: options.method === "GET" ? {} : options.data,
|
|
936
|
+
params: options.method === "GET" ? options.data : {},
|
|
937
|
+
signal: abortController?.signal,
|
|
938
|
+
headers: {
|
|
939
|
+
...options.headers,
|
|
940
|
+
...context.dialog ? { [DIALOG_KEY_HEADER]: context.dialog.key } : {},
|
|
941
|
+
...context.dialog ? { [DIALOG_REDIRECT_HEADER]: context.dialog.redirectUrl ?? "" } : {},
|
|
942
|
+
...when(options.only !== void 0 || options.except !== void 0, {
|
|
943
|
+
[PARTIAL_COMPONENT_HEADER]: context.view.component,
|
|
944
|
+
...when(options.only, { [ONLY_DATA_HEADER]: JSON.stringify(options.only) }, {}),
|
|
945
|
+
...when(options.except, { [EXCEPT_DATA_HEADER]: JSON.stringify(options.except) }, {})
|
|
946
|
+
}, {}),
|
|
947
|
+
...when(options.errorBag, { [ERROR_BAG_HEADER]: options.errorBag }, {}),
|
|
948
|
+
...when(context.version, { [VERSION_HEADER]: context.version }, {}),
|
|
949
|
+
[HYBRIDLY_HEADER]: true,
|
|
950
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
951
|
+
"Accept": "text/html, application/xhtml+xml"
|
|
952
|
+
},
|
|
953
|
+
validateStatus: () => true,
|
|
954
|
+
onUploadProgress: async (event) => {
|
|
955
|
+
await runHooks("progress", options.hooks, {
|
|
956
|
+
event,
|
|
957
|
+
percentage: Math.round(event.loaded / (event.total ?? 0) * 100)
|
|
958
|
+
}, context);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
836
961
|
}
|
|
837
962
|
async function initializeRouter() {
|
|
838
963
|
const context = getRouterContext();
|
|
@@ -875,4 +1000,4 @@ function can(resource, action) {
|
|
|
875
1000
|
return resource.authorization?.[action] ?? false;
|
|
876
1001
|
}
|
|
877
1002
|
|
|
878
|
-
export { can, constants, createRouter,
|
|
1003
|
+
export { can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridly/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Core functionality of Hybridly",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hybridly",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"qs": "^6.11.2",
|
|
40
|
-
"superjson": "^1.12.
|
|
41
|
-
"@hybridly/utils": "0.4.
|
|
40
|
+
"superjson": "^1.12.4",
|
|
41
|
+
"@hybridly/utils": "0.4.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"defu": "^6.1.2"
|