@hybridly/core 0.4.4 → 0.5.0
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 +76 -27
- package/dist/index.d.cts +34 -20
- package/dist/index.d.mts +34 -20
- package/dist/index.d.ts +34 -20
- package/dist/index.mjs +76 -27
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -18,14 +18,12 @@ const ONLY_DATA_HEADER = `${HYBRIDLY_HEADER}-only-data`;
|
|
|
18
18
|
const DIALOG_KEY_HEADER = `${HYBRIDLY_HEADER}-dialog-key`;
|
|
19
19
|
const DIALOG_REDIRECT_HEADER = `${HYBRIDLY_HEADER}-dialog-redirect`;
|
|
20
20
|
const EXCEPT_DATA_HEADER = `${HYBRIDLY_HEADER}-except-data`;
|
|
21
|
-
const CONTEXT_HEADER = `${HYBRIDLY_HEADER}-context`;
|
|
22
21
|
const VERSION_HEADER = `${HYBRIDLY_HEADER}-version`;
|
|
23
22
|
const ERROR_BAG_HEADER = `${HYBRIDLY_HEADER}-error-bag`;
|
|
24
23
|
const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
25
24
|
|
|
26
25
|
const constants = {
|
|
27
26
|
__proto__: null,
|
|
28
|
-
CONTEXT_HEADER: CONTEXT_HEADER,
|
|
29
27
|
DIALOG_KEY_HEADER: DIALOG_KEY_HEADER,
|
|
30
28
|
DIALOG_REDIRECT_HEADER: DIALOG_REDIRECT_HEADER,
|
|
31
29
|
ERROR_BAG_HEADER: ERROR_BAG_HEADER,
|
|
@@ -273,6 +271,7 @@ async function registerEventListeners() {
|
|
|
273
271
|
if (!state) {
|
|
274
272
|
utils.debug.history("There is no state. Adding hash if any and restoring scroll positions.");
|
|
275
273
|
return await navigate({
|
|
274
|
+
type: "initial",
|
|
276
275
|
payload: {
|
|
277
276
|
...context,
|
|
278
277
|
url: makeUrl(context.url, { hash: window.location.hash }).toString()
|
|
@@ -283,11 +282,11 @@ async function registerEventListeners() {
|
|
|
283
282
|
});
|
|
284
283
|
}
|
|
285
284
|
await navigate({
|
|
285
|
+
type: "back-forward",
|
|
286
286
|
payload: state,
|
|
287
287
|
preserveScroll: true,
|
|
288
288
|
preserveState: !!getInternalRouterContext().dialog || !!state.dialog,
|
|
289
|
-
updateHistoryState: false
|
|
290
|
-
isBackForward: true
|
|
289
|
+
updateHistoryState: false
|
|
291
290
|
});
|
|
292
291
|
});
|
|
293
292
|
window?.addEventListener("scroll", (event) => utils.debounce(() => {
|
|
@@ -310,14 +309,14 @@ async function handleBackForwardNavigation() {
|
|
|
310
309
|
throw new Error("Tried to handling a back/forward navigation, but there was no state in the history. This should not happen.");
|
|
311
310
|
}
|
|
312
311
|
await navigate({
|
|
312
|
+
type: "back-forward",
|
|
313
313
|
payload: {
|
|
314
314
|
...state,
|
|
315
315
|
version: context.version
|
|
316
316
|
},
|
|
317
317
|
preserveScroll: true,
|
|
318
318
|
preserveState: false,
|
|
319
|
-
updateHistoryState: false
|
|
320
|
-
isBackForward: true
|
|
319
|
+
updateHistoryState: false
|
|
321
320
|
});
|
|
322
321
|
}
|
|
323
322
|
function remember(key, value) {
|
|
@@ -607,8 +606,9 @@ async function handleExternalNavigation() {
|
|
|
607
606
|
url: makeUrl(getRouterContext().url, { hash: window.location.hash }).toString()
|
|
608
607
|
});
|
|
609
608
|
await navigate({
|
|
610
|
-
|
|
611
|
-
preserveState: true
|
|
609
|
+
type: "initial",
|
|
610
|
+
preserveState: true,
|
|
611
|
+
preserveScroll: options.preserveScroll
|
|
612
612
|
});
|
|
613
613
|
}
|
|
614
614
|
function isExternalNavigation() {
|
|
@@ -644,6 +644,27 @@ async function closeDialog(options) {
|
|
|
644
644
|
});
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
+
function isDownloadResponse(response) {
|
|
648
|
+
return response.status === 200 && !!response.headers["content-disposition"];
|
|
649
|
+
}
|
|
650
|
+
async function handleDownloadResponse(response) {
|
|
651
|
+
const blob = new Blob([response.data], { type: "application/octet-stream" });
|
|
652
|
+
const urlObject = window.webkitURL || window.URL;
|
|
653
|
+
const link = document.createElement("a");
|
|
654
|
+
link.style.display = "none";
|
|
655
|
+
link.href = urlObject.createObjectURL(blob);
|
|
656
|
+
link.download = getFileNameFromContentDispositionHeader(response.headers["content-disposition"]);
|
|
657
|
+
link.click();
|
|
658
|
+
setTimeout(() => {
|
|
659
|
+
urlObject.revokeObjectURL(link.href);
|
|
660
|
+
link.remove();
|
|
661
|
+
}, 0);
|
|
662
|
+
}
|
|
663
|
+
function getFileNameFromContentDispositionHeader(header) {
|
|
664
|
+
const result = header.split(";")[1]?.trim().split("=")[1];
|
|
665
|
+
return result?.replace(/^"(.*)"$/, "$1") ?? "";
|
|
666
|
+
}
|
|
667
|
+
|
|
647
668
|
function isPreloaded(targetUrl) {
|
|
648
669
|
const context = getInternalRouterContext();
|
|
649
670
|
return context.preloadCache.has(targetUrl.toString()) ?? false;
|
|
@@ -765,10 +786,7 @@ async function performHybridNavigation(options) {
|
|
|
765
786
|
context.pendingNavigation?.controller?.abort();
|
|
766
787
|
}
|
|
767
788
|
saveScrollPositions();
|
|
768
|
-
|
|
769
|
-
options.url = makeUrl(options.url, options.transformUrl);
|
|
770
|
-
}
|
|
771
|
-
const targetUrl = makeUrl(options.url ?? context.url);
|
|
789
|
+
const targetUrl = makeUrl(options.url ?? context.url, options.transformUrl);
|
|
772
790
|
const abortController = new AbortController();
|
|
773
791
|
setContext({
|
|
774
792
|
pendingNavigation: {
|
|
@@ -782,7 +800,10 @@ async function performHybridNavigation(options) {
|
|
|
782
800
|
await runHooks("start", options.hooks, context);
|
|
783
801
|
utils.debug.router("Making request with axios.");
|
|
784
802
|
const response = await performHybridRequest(targetUrl, options, abortController);
|
|
785
|
-
await runHooks("data", options.hooks, response, context);
|
|
803
|
+
const result = await runHooks("data", options.hooks, response, context);
|
|
804
|
+
if (result === false) {
|
|
805
|
+
return { response };
|
|
806
|
+
}
|
|
786
807
|
if (isExternalResponse(response)) {
|
|
787
808
|
utils.debug.router("The response is explicitely external.");
|
|
788
809
|
await performExternalNavigation({
|
|
@@ -791,6 +812,11 @@ async function performHybridNavigation(options) {
|
|
|
791
812
|
});
|
|
792
813
|
return { response };
|
|
793
814
|
}
|
|
815
|
+
if (isDownloadResponse(response)) {
|
|
816
|
+
utils.debug.router("The response returns a file to download.");
|
|
817
|
+
await handleDownloadResponse(response);
|
|
818
|
+
return { response };
|
|
819
|
+
}
|
|
794
820
|
if (!isHybridResponse(response)) {
|
|
795
821
|
throw new NotAHybridResponseError(response);
|
|
796
822
|
}
|
|
@@ -802,6 +828,7 @@ async function performHybridNavigation(options) {
|
|
|
802
828
|
utils.debug.router("Merged properties:", payload.view.properties);
|
|
803
829
|
}
|
|
804
830
|
await navigate({
|
|
831
|
+
type: "server",
|
|
805
832
|
payload: {
|
|
806
833
|
...payload,
|
|
807
834
|
url: fillHash(targetUrl, payload.url)
|
|
@@ -885,6 +912,7 @@ function isHybridResponse(response) {
|
|
|
885
912
|
}
|
|
886
913
|
async function navigate(options) {
|
|
887
914
|
const context = getRouterContext();
|
|
915
|
+
options.hasDialog ?? (options.hasDialog = !!options.payload?.dialog);
|
|
888
916
|
utils.debug.router("Making an internal navigation:", { context, options });
|
|
889
917
|
await runHooks("navigating", {}, options, context);
|
|
890
918
|
options.payload ?? (options.payload = payloadFromContext());
|
|
@@ -895,6 +923,7 @@ async function navigate(options) {
|
|
|
895
923
|
const shouldPreserveScroll = evaluateConditionalOption(options.preserveScroll);
|
|
896
924
|
const shouldReplaceHistory = evaluateConditionalOption(options.replace);
|
|
897
925
|
const shouldReplaceUrl = evaluateConditionalOption(options.preserveUrl);
|
|
926
|
+
const shouldPreserveView = !options.payload.view.component;
|
|
898
927
|
if (shouldPreserveState && getHistoryMemo() && options.payload.view.component === context.view.component) {
|
|
899
928
|
utils.debug.history("Setting the memo from this history entry into the current context.");
|
|
900
929
|
setContext({ memo: getHistoryMemo() });
|
|
@@ -903,26 +932,44 @@ async function navigate(options) {
|
|
|
903
932
|
utils.debug.router(`Preserving the current URL (${context.url}) instead of navigating to ${options.payload.url}`);
|
|
904
933
|
options.payload.url = context.url;
|
|
905
934
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
935
|
+
const payload = shouldPreserveView ? {
|
|
936
|
+
view: {
|
|
937
|
+
component: context.view.component,
|
|
938
|
+
properties: utils.merge(context.view.properties, options.payload.view.properties),
|
|
939
|
+
deferred: context.view.deferred
|
|
940
|
+
},
|
|
941
|
+
url: context.url,
|
|
942
|
+
version: options.payload.version,
|
|
943
|
+
dialog: context.dialog
|
|
944
|
+
} : options.payload;
|
|
945
|
+
setContext({ ...payload, memo: {} });
|
|
910
946
|
if (options.updateHistoryState !== false) {
|
|
911
947
|
utils.debug.router(`Target URL is ${context.url}, current window URL is ${window.location.href}.`, { shouldReplaceHistory });
|
|
912
948
|
setHistoryState({ replace: shouldReplaceHistory });
|
|
913
949
|
}
|
|
914
|
-
context.
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
950
|
+
if (context.view.deferred?.length) {
|
|
951
|
+
utils.debug.router("Request has deferred properties, queueing a partial reload:", context.view.deferred);
|
|
952
|
+
context.adapter.executeOnMounted(async () => {
|
|
953
|
+
await performHybridNavigation({
|
|
954
|
+
preserveScroll: true,
|
|
955
|
+
preserveState: true,
|
|
956
|
+
replace: true,
|
|
957
|
+
only: context.view.deferred
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const viewComponent = !shouldPreserveView ? await context.adapter.resolveComponent(context.view.component) : void 0;
|
|
962
|
+
if (viewComponent) {
|
|
963
|
+
utils.debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
|
|
964
|
+
}
|
|
919
965
|
await context.adapter.onViewSwap({
|
|
920
966
|
component: viewComponent,
|
|
921
967
|
dialog: context.dialog,
|
|
922
968
|
properties: options.payload?.view?.properties,
|
|
923
|
-
preserveState: shouldPreserveState
|
|
969
|
+
preserveState: shouldPreserveState,
|
|
970
|
+
onMounted: (hookOptions) => runHooks("mounted", {}, { ...options, ...hookOptions }, context)
|
|
924
971
|
});
|
|
925
|
-
if (options.
|
|
972
|
+
if (options.type === "back-forward") {
|
|
926
973
|
restoreScrollPositions();
|
|
927
974
|
} else if (!shouldPreserveScroll) {
|
|
928
975
|
resetScrollPositions();
|
|
@@ -974,12 +1021,12 @@ async function initializeRouter() {
|
|
|
974
1021
|
} else if (isExternalNavigation()) {
|
|
975
1022
|
handleExternalNavigation();
|
|
976
1023
|
} else {
|
|
977
|
-
utils.debug.router("Handling the initial
|
|
1024
|
+
utils.debug.router("Handling the initial navigation.");
|
|
978
1025
|
setContext({
|
|
979
1026
|
url: makeUrl(context.url, { hash: window.location.hash }).toString()
|
|
980
1027
|
});
|
|
981
1028
|
await navigate({
|
|
982
|
-
|
|
1029
|
+
type: "initial",
|
|
983
1030
|
preserveState: true,
|
|
984
1031
|
replace: sameUrls(context.url, window.location.href)
|
|
985
1032
|
});
|
|
@@ -993,13 +1040,15 @@ async function performLocalNavigation(targetUrl, options) {
|
|
|
993
1040
|
const url = normalizeUrl(targetUrl);
|
|
994
1041
|
return await navigate({
|
|
995
1042
|
...options,
|
|
1043
|
+
type: "local",
|
|
996
1044
|
payload: {
|
|
997
1045
|
version: context.version,
|
|
998
1046
|
dialog: options?.dialog === false ? void 0 : options?.dialog ?? context.dialog,
|
|
999
1047
|
url,
|
|
1000
1048
|
view: {
|
|
1001
1049
|
component: options?.component ?? context.view.component,
|
|
1002
|
-
properties: options?.properties ?? {}
|
|
1050
|
+
properties: options?.properties ?? {},
|
|
1051
|
+
deferred: []
|
|
1003
1052
|
}
|
|
1004
1053
|
}
|
|
1005
1054
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -55,7 +55,7 @@ interface Hooks extends RequestHooks {
|
|
|
55
55
|
*/
|
|
56
56
|
initialized: (context: InternalRouterContext) => MaybePromise<any>;
|
|
57
57
|
/**
|
|
58
|
-
* Called after Hybridly's initial
|
|
58
|
+
* Called after Hybridly's initial load.
|
|
59
59
|
*/
|
|
60
60
|
ready: (context: InternalRouterContext) => MaybePromise<any>;
|
|
61
61
|
/**
|
|
@@ -65,15 +65,21 @@ interface Hooks extends RequestHooks {
|
|
|
65
65
|
/**
|
|
66
66
|
* Called when a component navigation is being made.
|
|
67
67
|
*/
|
|
68
|
-
navigating: (options:
|
|
68
|
+
navigating: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
69
69
|
/**
|
|
70
70
|
* Called when a component has been navigated to.
|
|
71
71
|
*/
|
|
72
|
-
navigated: (options:
|
|
72
|
+
navigated: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
73
73
|
/**
|
|
74
74
|
* Called when a component has been navigated to and was mounted by the adapter.
|
|
75
75
|
*/
|
|
76
|
-
mounted: (context: InternalRouterContext) => MaybePromise<any>;
|
|
76
|
+
mounted: (options: InternalNavigationOptions & MountedHookOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
77
|
+
}
|
|
78
|
+
interface MountedHookOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Whether the component being mounted is a dialog.
|
|
81
|
+
*/
|
|
82
|
+
isDialog: boolean;
|
|
77
83
|
}
|
|
78
84
|
interface HookOptions {
|
|
79
85
|
/** Executes the hook only once. */
|
|
@@ -128,7 +134,7 @@ interface ComponentNavigationOptions {
|
|
|
128
134
|
replace?: ConditionalNavigationOption<boolean>;
|
|
129
135
|
/** Whether to preserve the current scrollbar position. */
|
|
130
136
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
131
|
-
/** Whether to preserve the current
|
|
137
|
+
/** Whether to preserve the current view component state. */
|
|
132
138
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
133
139
|
}
|
|
134
140
|
interface NavigationOptions {
|
|
@@ -139,9 +145,9 @@ interface NavigationOptions {
|
|
|
139
145
|
* one. This affects the browser's "back" and "forward" features.
|
|
140
146
|
*/
|
|
141
147
|
replace?: ConditionalNavigationOption<boolean>;
|
|
142
|
-
/** Whether to preserve the scrollbars positions on the
|
|
148
|
+
/** Whether to preserve the scrollbars positions on the view. */
|
|
143
149
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
144
|
-
/** Whether to preserve the current
|
|
150
|
+
/** Whether to preserve the current view component's state. */
|
|
145
151
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
146
152
|
/** Whether to preserve the current URL. */
|
|
147
153
|
preserveUrl?: ConditionalNavigationOption<boolean>;
|
|
@@ -160,16 +166,22 @@ interface NavigationOptions {
|
|
|
160
166
|
* @internal This is an advanced property meant to be used internally.
|
|
161
167
|
*/
|
|
162
168
|
updateHistoryState?: boolean;
|
|
169
|
+
}
|
|
170
|
+
interface InternalNavigationOptions extends NavigationOptions {
|
|
163
171
|
/**
|
|
164
|
-
* Defines
|
|
165
|
-
*
|
|
172
|
+
* Defines the kind of navigation being performed.
|
|
173
|
+
* - initial: the initial load's navigation
|
|
174
|
+
* - server: a navigation initiated by a server round-trip
|
|
175
|
+
* - local: a navigation initiated by `router.local`
|
|
176
|
+
* - back-forward: a navigation initiated by the browser's `popstate` event
|
|
177
|
+
* @internal
|
|
166
178
|
*/
|
|
167
|
-
|
|
179
|
+
type: 'initial' | 'local' | 'back-forward' | 'server';
|
|
168
180
|
/**
|
|
169
|
-
* Defines whether this navigation
|
|
170
|
-
* @internal
|
|
181
|
+
* Defines whether this navigation opens a dialog.
|
|
182
|
+
* @internal
|
|
171
183
|
*/
|
|
172
|
-
|
|
184
|
+
hasDialog?: boolean;
|
|
173
185
|
}
|
|
174
186
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
175
187
|
interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
|
|
@@ -266,15 +278,17 @@ interface PendingNavigation {
|
|
|
266
278
|
/** Current status. */
|
|
267
279
|
status: 'pending' | 'success' | 'error';
|
|
268
280
|
}
|
|
269
|
-
/** A
|
|
281
|
+
/** A view or dialog component. */
|
|
270
282
|
interface View {
|
|
271
283
|
/** Name of the component to use. */
|
|
272
|
-
component
|
|
284
|
+
component?: string;
|
|
273
285
|
/** Properties to apply to the component. */
|
|
274
286
|
properties: Properties;
|
|
287
|
+
/** Deferred properties for this view. */
|
|
288
|
+
deferred: string[];
|
|
275
289
|
}
|
|
276
|
-
interface Dialog extends View {
|
|
277
|
-
/** URL that is the base background
|
|
290
|
+
interface Dialog extends Required<View> {
|
|
291
|
+
/** URL that is the base background view when navigating to the dialog directly. */
|
|
278
292
|
baseUrl: string;
|
|
279
293
|
/** URL to which the dialog should redirect when closed. */
|
|
280
294
|
redirectUrl: string;
|
|
@@ -294,6 +308,8 @@ interface SwapOptions<T> {
|
|
|
294
308
|
preserveState?: boolean;
|
|
295
309
|
/** Current dialog. */
|
|
296
310
|
dialog?: Dialog;
|
|
311
|
+
/** On mounted callback. */
|
|
312
|
+
onMounted?: (options: MountedHookOptions) => void;
|
|
297
313
|
}
|
|
298
314
|
type ViewComponent = any;
|
|
299
315
|
type ResolveComponent = (name: string) => Promise<ViewComponent>;
|
|
@@ -471,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
|
|
|
471
487
|
declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
|
|
472
488
|
declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
|
|
473
489
|
declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
|
|
474
|
-
declare const CONTEXT_HEADER = "x-hybrid-context";
|
|
475
490
|
declare const VERSION_HEADER = "x-hybrid-version";
|
|
476
491
|
declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
|
|
477
492
|
declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
478
493
|
|
|
479
|
-
declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
|
|
480
494
|
declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
|
|
481
495
|
declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
|
|
482
496
|
declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
|
|
@@ -489,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
|
|
|
489
503
|
declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
|
|
490
504
|
declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
|
|
491
505
|
declare namespace constants {
|
|
492
|
-
export {
|
|
506
|
+
export { constants_DIALOG_KEY_HEADER as DIALOG_KEY_HEADER, constants_DIALOG_REDIRECT_HEADER as DIALOG_REDIRECT_HEADER, constants_ERROR_BAG_HEADER as ERROR_BAG_HEADER, constants_EXCEPT_DATA_HEADER as EXCEPT_DATA_HEADER, constants_EXTERNAL_NAVIGATION_HEADER as EXTERNAL_NAVIGATION_HEADER, constants_HYBRIDLY_HEADER as HYBRIDLY_HEADER, constants_ONLY_DATA_HEADER as ONLY_DATA_HEADER, constants_PARTIAL_COMPONENT_HEADER as PARTIAL_COMPONENT_HEADER, constants_SCROLL_REGION_ATTRIBUTE as SCROLL_REGION_ATTRIBUTE, constants_STORAGE_EXTERNAL_KEY as STORAGE_EXTERNAL_KEY, constants_VERSION_HEADER as VERSION_HEADER };
|
|
493
507
|
}
|
|
494
508
|
|
|
495
509
|
export { type Authorizable, type DynamicConfiguration, type GlobalRouteCollection, type HybridPayload, type HybridRequestOptions, type MaybePromise, type Method, type NavigationResponse, type Plugin, type Progress, type ResolveComponent, type RouteDefinition, type RouteName, type RouteParameters, type Router, type RouterContext, type RouterContextOptions, type RoutingConfiguration, type UrlResolvable, can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/dist/index.d.mts
CHANGED
|
@@ -55,7 +55,7 @@ interface Hooks extends RequestHooks {
|
|
|
55
55
|
*/
|
|
56
56
|
initialized: (context: InternalRouterContext) => MaybePromise<any>;
|
|
57
57
|
/**
|
|
58
|
-
* Called after Hybridly's initial
|
|
58
|
+
* Called after Hybridly's initial load.
|
|
59
59
|
*/
|
|
60
60
|
ready: (context: InternalRouterContext) => MaybePromise<any>;
|
|
61
61
|
/**
|
|
@@ -65,15 +65,21 @@ interface Hooks extends RequestHooks {
|
|
|
65
65
|
/**
|
|
66
66
|
* Called when a component navigation is being made.
|
|
67
67
|
*/
|
|
68
|
-
navigating: (options:
|
|
68
|
+
navigating: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
69
69
|
/**
|
|
70
70
|
* Called when a component has been navigated to.
|
|
71
71
|
*/
|
|
72
|
-
navigated: (options:
|
|
72
|
+
navigated: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
73
73
|
/**
|
|
74
74
|
* Called when a component has been navigated to and was mounted by the adapter.
|
|
75
75
|
*/
|
|
76
|
-
mounted: (context: InternalRouterContext) => MaybePromise<any>;
|
|
76
|
+
mounted: (options: InternalNavigationOptions & MountedHookOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
77
|
+
}
|
|
78
|
+
interface MountedHookOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Whether the component being mounted is a dialog.
|
|
81
|
+
*/
|
|
82
|
+
isDialog: boolean;
|
|
77
83
|
}
|
|
78
84
|
interface HookOptions {
|
|
79
85
|
/** Executes the hook only once. */
|
|
@@ -128,7 +134,7 @@ interface ComponentNavigationOptions {
|
|
|
128
134
|
replace?: ConditionalNavigationOption<boolean>;
|
|
129
135
|
/** Whether to preserve the current scrollbar position. */
|
|
130
136
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
131
|
-
/** Whether to preserve the current
|
|
137
|
+
/** Whether to preserve the current view component state. */
|
|
132
138
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
133
139
|
}
|
|
134
140
|
interface NavigationOptions {
|
|
@@ -139,9 +145,9 @@ interface NavigationOptions {
|
|
|
139
145
|
* one. This affects the browser's "back" and "forward" features.
|
|
140
146
|
*/
|
|
141
147
|
replace?: ConditionalNavigationOption<boolean>;
|
|
142
|
-
/** Whether to preserve the scrollbars positions on the
|
|
148
|
+
/** Whether to preserve the scrollbars positions on the view. */
|
|
143
149
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
144
|
-
/** Whether to preserve the current
|
|
150
|
+
/** Whether to preserve the current view component's state. */
|
|
145
151
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
146
152
|
/** Whether to preserve the current URL. */
|
|
147
153
|
preserveUrl?: ConditionalNavigationOption<boolean>;
|
|
@@ -160,16 +166,22 @@ interface NavigationOptions {
|
|
|
160
166
|
* @internal This is an advanced property meant to be used internally.
|
|
161
167
|
*/
|
|
162
168
|
updateHistoryState?: boolean;
|
|
169
|
+
}
|
|
170
|
+
interface InternalNavigationOptions extends NavigationOptions {
|
|
163
171
|
/**
|
|
164
|
-
* Defines
|
|
165
|
-
*
|
|
172
|
+
* Defines the kind of navigation being performed.
|
|
173
|
+
* - initial: the initial load's navigation
|
|
174
|
+
* - server: a navigation initiated by a server round-trip
|
|
175
|
+
* - local: a navigation initiated by `router.local`
|
|
176
|
+
* - back-forward: a navigation initiated by the browser's `popstate` event
|
|
177
|
+
* @internal
|
|
166
178
|
*/
|
|
167
|
-
|
|
179
|
+
type: 'initial' | 'local' | 'back-forward' | 'server';
|
|
168
180
|
/**
|
|
169
|
-
* Defines whether this navigation
|
|
170
|
-
* @internal
|
|
181
|
+
* Defines whether this navigation opens a dialog.
|
|
182
|
+
* @internal
|
|
171
183
|
*/
|
|
172
|
-
|
|
184
|
+
hasDialog?: boolean;
|
|
173
185
|
}
|
|
174
186
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
175
187
|
interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
|
|
@@ -266,15 +278,17 @@ interface PendingNavigation {
|
|
|
266
278
|
/** Current status. */
|
|
267
279
|
status: 'pending' | 'success' | 'error';
|
|
268
280
|
}
|
|
269
|
-
/** A
|
|
281
|
+
/** A view or dialog component. */
|
|
270
282
|
interface View {
|
|
271
283
|
/** Name of the component to use. */
|
|
272
|
-
component
|
|
284
|
+
component?: string;
|
|
273
285
|
/** Properties to apply to the component. */
|
|
274
286
|
properties: Properties;
|
|
287
|
+
/** Deferred properties for this view. */
|
|
288
|
+
deferred: string[];
|
|
275
289
|
}
|
|
276
|
-
interface Dialog extends View {
|
|
277
|
-
/** URL that is the base background
|
|
290
|
+
interface Dialog extends Required<View> {
|
|
291
|
+
/** URL that is the base background view when navigating to the dialog directly. */
|
|
278
292
|
baseUrl: string;
|
|
279
293
|
/** URL to which the dialog should redirect when closed. */
|
|
280
294
|
redirectUrl: string;
|
|
@@ -294,6 +308,8 @@ interface SwapOptions<T> {
|
|
|
294
308
|
preserveState?: boolean;
|
|
295
309
|
/** Current dialog. */
|
|
296
310
|
dialog?: Dialog;
|
|
311
|
+
/** On mounted callback. */
|
|
312
|
+
onMounted?: (options: MountedHookOptions) => void;
|
|
297
313
|
}
|
|
298
314
|
type ViewComponent = any;
|
|
299
315
|
type ResolveComponent = (name: string) => Promise<ViewComponent>;
|
|
@@ -471,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
|
|
|
471
487
|
declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
|
|
472
488
|
declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
|
|
473
489
|
declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
|
|
474
|
-
declare const CONTEXT_HEADER = "x-hybrid-context";
|
|
475
490
|
declare const VERSION_HEADER = "x-hybrid-version";
|
|
476
491
|
declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
|
|
477
492
|
declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
478
493
|
|
|
479
|
-
declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
|
|
480
494
|
declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
|
|
481
495
|
declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
|
|
482
496
|
declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
|
|
@@ -489,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
|
|
|
489
503
|
declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
|
|
490
504
|
declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
|
|
491
505
|
declare namespace constants {
|
|
492
|
-
export {
|
|
506
|
+
export { constants_DIALOG_KEY_HEADER as DIALOG_KEY_HEADER, constants_DIALOG_REDIRECT_HEADER as DIALOG_REDIRECT_HEADER, constants_ERROR_BAG_HEADER as ERROR_BAG_HEADER, constants_EXCEPT_DATA_HEADER as EXCEPT_DATA_HEADER, constants_EXTERNAL_NAVIGATION_HEADER as EXTERNAL_NAVIGATION_HEADER, constants_HYBRIDLY_HEADER as HYBRIDLY_HEADER, constants_ONLY_DATA_HEADER as ONLY_DATA_HEADER, constants_PARTIAL_COMPONENT_HEADER as PARTIAL_COMPONENT_HEADER, constants_SCROLL_REGION_ATTRIBUTE as SCROLL_REGION_ATTRIBUTE, constants_STORAGE_EXTERNAL_KEY as STORAGE_EXTERNAL_KEY, constants_VERSION_HEADER as VERSION_HEADER };
|
|
493
507
|
}
|
|
494
508
|
|
|
495
509
|
export { type Authorizable, type DynamicConfiguration, type GlobalRouteCollection, type HybridPayload, type HybridRequestOptions, type MaybePromise, type Method, type NavigationResponse, type Plugin, type Progress, type ResolveComponent, type RouteDefinition, type RouteName, type RouteParameters, type Router, type RouterContext, type RouterContextOptions, type RoutingConfiguration, type UrlResolvable, can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/dist/index.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ interface Hooks extends RequestHooks {
|
|
|
55
55
|
*/
|
|
56
56
|
initialized: (context: InternalRouterContext) => MaybePromise<any>;
|
|
57
57
|
/**
|
|
58
|
-
* Called after Hybridly's initial
|
|
58
|
+
* Called after Hybridly's initial load.
|
|
59
59
|
*/
|
|
60
60
|
ready: (context: InternalRouterContext) => MaybePromise<any>;
|
|
61
61
|
/**
|
|
@@ -65,15 +65,21 @@ interface Hooks extends RequestHooks {
|
|
|
65
65
|
/**
|
|
66
66
|
* Called when a component navigation is being made.
|
|
67
67
|
*/
|
|
68
|
-
navigating: (options:
|
|
68
|
+
navigating: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
69
69
|
/**
|
|
70
70
|
* Called when a component has been navigated to.
|
|
71
71
|
*/
|
|
72
|
-
navigated: (options:
|
|
72
|
+
navigated: (options: InternalNavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
73
73
|
/**
|
|
74
74
|
* Called when a component has been navigated to and was mounted by the adapter.
|
|
75
75
|
*/
|
|
76
|
-
mounted: (context: InternalRouterContext) => MaybePromise<any>;
|
|
76
|
+
mounted: (options: InternalNavigationOptions & MountedHookOptions, context: InternalRouterContext) => MaybePromise<any>;
|
|
77
|
+
}
|
|
78
|
+
interface MountedHookOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Whether the component being mounted is a dialog.
|
|
81
|
+
*/
|
|
82
|
+
isDialog: boolean;
|
|
77
83
|
}
|
|
78
84
|
interface HookOptions {
|
|
79
85
|
/** Executes the hook only once. */
|
|
@@ -128,7 +134,7 @@ interface ComponentNavigationOptions {
|
|
|
128
134
|
replace?: ConditionalNavigationOption<boolean>;
|
|
129
135
|
/** Whether to preserve the current scrollbar position. */
|
|
130
136
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
131
|
-
/** Whether to preserve the current
|
|
137
|
+
/** Whether to preserve the current view component state. */
|
|
132
138
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
133
139
|
}
|
|
134
140
|
interface NavigationOptions {
|
|
@@ -139,9 +145,9 @@ interface NavigationOptions {
|
|
|
139
145
|
* one. This affects the browser's "back" and "forward" features.
|
|
140
146
|
*/
|
|
141
147
|
replace?: ConditionalNavigationOption<boolean>;
|
|
142
|
-
/** Whether to preserve the scrollbars positions on the
|
|
148
|
+
/** Whether to preserve the scrollbars positions on the view. */
|
|
143
149
|
preserveScroll?: ConditionalNavigationOption<boolean>;
|
|
144
|
-
/** Whether to preserve the current
|
|
150
|
+
/** Whether to preserve the current view component's state. */
|
|
145
151
|
preserveState?: ConditionalNavigationOption<boolean>;
|
|
146
152
|
/** Whether to preserve the current URL. */
|
|
147
153
|
preserveUrl?: ConditionalNavigationOption<boolean>;
|
|
@@ -160,16 +166,22 @@ interface NavigationOptions {
|
|
|
160
166
|
* @internal This is an advanced property meant to be used internally.
|
|
161
167
|
*/
|
|
162
168
|
updateHistoryState?: boolean;
|
|
169
|
+
}
|
|
170
|
+
interface InternalNavigationOptions extends NavigationOptions {
|
|
163
171
|
/**
|
|
164
|
-
* Defines
|
|
165
|
-
*
|
|
172
|
+
* Defines the kind of navigation being performed.
|
|
173
|
+
* - initial: the initial load's navigation
|
|
174
|
+
* - server: a navigation initiated by a server round-trip
|
|
175
|
+
* - local: a navigation initiated by `router.local`
|
|
176
|
+
* - back-forward: a navigation initiated by the browser's `popstate` event
|
|
177
|
+
* @internal
|
|
166
178
|
*/
|
|
167
|
-
|
|
179
|
+
type: 'initial' | 'local' | 'back-forward' | 'server';
|
|
168
180
|
/**
|
|
169
|
-
* Defines whether this navigation
|
|
170
|
-
* @internal
|
|
181
|
+
* Defines whether this navigation opens a dialog.
|
|
182
|
+
* @internal
|
|
171
183
|
*/
|
|
172
|
-
|
|
184
|
+
hasDialog?: boolean;
|
|
173
185
|
}
|
|
174
186
|
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
175
187
|
interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
|
|
@@ -266,15 +278,17 @@ interface PendingNavigation {
|
|
|
266
278
|
/** Current status. */
|
|
267
279
|
status: 'pending' | 'success' | 'error';
|
|
268
280
|
}
|
|
269
|
-
/** A
|
|
281
|
+
/** A view or dialog component. */
|
|
270
282
|
interface View {
|
|
271
283
|
/** Name of the component to use. */
|
|
272
|
-
component
|
|
284
|
+
component?: string;
|
|
273
285
|
/** Properties to apply to the component. */
|
|
274
286
|
properties: Properties;
|
|
287
|
+
/** Deferred properties for this view. */
|
|
288
|
+
deferred: string[];
|
|
275
289
|
}
|
|
276
|
-
interface Dialog extends View {
|
|
277
|
-
/** URL that is the base background
|
|
290
|
+
interface Dialog extends Required<View> {
|
|
291
|
+
/** URL that is the base background view when navigating to the dialog directly. */
|
|
278
292
|
baseUrl: string;
|
|
279
293
|
/** URL to which the dialog should redirect when closed. */
|
|
280
294
|
redirectUrl: string;
|
|
@@ -294,6 +308,8 @@ interface SwapOptions<T> {
|
|
|
294
308
|
preserveState?: boolean;
|
|
295
309
|
/** Current dialog. */
|
|
296
310
|
dialog?: Dialog;
|
|
311
|
+
/** On mounted callback. */
|
|
312
|
+
onMounted?: (options: MountedHookOptions) => void;
|
|
297
313
|
}
|
|
298
314
|
type ViewComponent = any;
|
|
299
315
|
type ResolveComponent = (name: string) => Promise<ViewComponent>;
|
|
@@ -471,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
|
|
|
471
487
|
declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
|
|
472
488
|
declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
|
|
473
489
|
declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
|
|
474
|
-
declare const CONTEXT_HEADER = "x-hybrid-context";
|
|
475
490
|
declare const VERSION_HEADER = "x-hybrid-version";
|
|
476
491
|
declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
|
|
477
492
|
declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
478
493
|
|
|
479
|
-
declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
|
|
480
494
|
declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
|
|
481
495
|
declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
|
|
482
496
|
declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
|
|
@@ -489,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
|
|
|
489
503
|
declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
|
|
490
504
|
declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
|
|
491
505
|
declare namespace constants {
|
|
492
|
-
export {
|
|
506
|
+
export { constants_DIALOG_KEY_HEADER as DIALOG_KEY_HEADER, constants_DIALOG_REDIRECT_HEADER as DIALOG_REDIRECT_HEADER, constants_ERROR_BAG_HEADER as ERROR_BAG_HEADER, constants_EXCEPT_DATA_HEADER as EXCEPT_DATA_HEADER, constants_EXTERNAL_NAVIGATION_HEADER as EXTERNAL_NAVIGATION_HEADER, constants_HYBRIDLY_HEADER as HYBRIDLY_HEADER, constants_ONLY_DATA_HEADER as ONLY_DATA_HEADER, constants_PARTIAL_COMPONENT_HEADER as PARTIAL_COMPONENT_HEADER, constants_SCROLL_REGION_ATTRIBUTE as SCROLL_REGION_ATTRIBUTE, constants_STORAGE_EXTERNAL_KEY as STORAGE_EXTERNAL_KEY, constants_VERSION_HEADER as VERSION_HEADER };
|
|
493
507
|
}
|
|
494
508
|
|
|
495
509
|
export { type Authorizable, type DynamicConfiguration, type GlobalRouteCollection, type HybridPayload, type HybridRequestOptions, type MaybePromise, type Method, type NavigationResponse, type Plugin, type Progress, type ResolveComponent, type RouteDefinition, type RouteName, type RouteParameters, type Router, type RouterContext, type RouterContextOptions, type RoutingConfiguration, type UrlResolvable, can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
|
package/dist/index.mjs
CHANGED
|
@@ -11,14 +11,12 @@ const ONLY_DATA_HEADER = `${HYBRIDLY_HEADER}-only-data`;
|
|
|
11
11
|
const DIALOG_KEY_HEADER = `${HYBRIDLY_HEADER}-dialog-key`;
|
|
12
12
|
const DIALOG_REDIRECT_HEADER = `${HYBRIDLY_HEADER}-dialog-redirect`;
|
|
13
13
|
const EXCEPT_DATA_HEADER = `${HYBRIDLY_HEADER}-except-data`;
|
|
14
|
-
const CONTEXT_HEADER = `${HYBRIDLY_HEADER}-context`;
|
|
15
14
|
const VERSION_HEADER = `${HYBRIDLY_HEADER}-version`;
|
|
16
15
|
const ERROR_BAG_HEADER = `${HYBRIDLY_HEADER}-error-bag`;
|
|
17
16
|
const SCROLL_REGION_ATTRIBUTE = "scroll-region";
|
|
18
17
|
|
|
19
18
|
const constants = {
|
|
20
19
|
__proto__: null,
|
|
21
|
-
CONTEXT_HEADER: CONTEXT_HEADER,
|
|
22
20
|
DIALOG_KEY_HEADER: DIALOG_KEY_HEADER,
|
|
23
21
|
DIALOG_REDIRECT_HEADER: DIALOG_REDIRECT_HEADER,
|
|
24
22
|
ERROR_BAG_HEADER: ERROR_BAG_HEADER,
|
|
@@ -266,6 +264,7 @@ async function registerEventListeners() {
|
|
|
266
264
|
if (!state) {
|
|
267
265
|
debug.history("There is no state. Adding hash if any and restoring scroll positions.");
|
|
268
266
|
return await navigate({
|
|
267
|
+
type: "initial",
|
|
269
268
|
payload: {
|
|
270
269
|
...context,
|
|
271
270
|
url: makeUrl(context.url, { hash: window.location.hash }).toString()
|
|
@@ -276,11 +275,11 @@ async function registerEventListeners() {
|
|
|
276
275
|
});
|
|
277
276
|
}
|
|
278
277
|
await navigate({
|
|
278
|
+
type: "back-forward",
|
|
279
279
|
payload: state,
|
|
280
280
|
preserveScroll: true,
|
|
281
281
|
preserveState: !!getInternalRouterContext().dialog || !!state.dialog,
|
|
282
|
-
updateHistoryState: false
|
|
283
|
-
isBackForward: true
|
|
282
|
+
updateHistoryState: false
|
|
284
283
|
});
|
|
285
284
|
});
|
|
286
285
|
window?.addEventListener("scroll", (event) => debounce(() => {
|
|
@@ -303,14 +302,14 @@ async function handleBackForwardNavigation() {
|
|
|
303
302
|
throw new Error("Tried to handling a back/forward navigation, but there was no state in the history. This should not happen.");
|
|
304
303
|
}
|
|
305
304
|
await navigate({
|
|
305
|
+
type: "back-forward",
|
|
306
306
|
payload: {
|
|
307
307
|
...state,
|
|
308
308
|
version: context.version
|
|
309
309
|
},
|
|
310
310
|
preserveScroll: true,
|
|
311
311
|
preserveState: false,
|
|
312
|
-
updateHistoryState: false
|
|
313
|
-
isBackForward: true
|
|
312
|
+
updateHistoryState: false
|
|
314
313
|
});
|
|
315
314
|
}
|
|
316
315
|
function remember(key, value) {
|
|
@@ -600,8 +599,9 @@ async function handleExternalNavigation() {
|
|
|
600
599
|
url: makeUrl(getRouterContext().url, { hash: window.location.hash }).toString()
|
|
601
600
|
});
|
|
602
601
|
await navigate({
|
|
603
|
-
|
|
604
|
-
preserveState: true
|
|
602
|
+
type: "initial",
|
|
603
|
+
preserveState: true,
|
|
604
|
+
preserveScroll: options.preserveScroll
|
|
605
605
|
});
|
|
606
606
|
}
|
|
607
607
|
function isExternalNavigation() {
|
|
@@ -637,6 +637,27 @@ async function closeDialog(options) {
|
|
|
637
637
|
});
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
+
function isDownloadResponse(response) {
|
|
641
|
+
return response.status === 200 && !!response.headers["content-disposition"];
|
|
642
|
+
}
|
|
643
|
+
async function handleDownloadResponse(response) {
|
|
644
|
+
const blob = new Blob([response.data], { type: "application/octet-stream" });
|
|
645
|
+
const urlObject = window.webkitURL || window.URL;
|
|
646
|
+
const link = document.createElement("a");
|
|
647
|
+
link.style.display = "none";
|
|
648
|
+
link.href = urlObject.createObjectURL(blob);
|
|
649
|
+
link.download = getFileNameFromContentDispositionHeader(response.headers["content-disposition"]);
|
|
650
|
+
link.click();
|
|
651
|
+
setTimeout(() => {
|
|
652
|
+
urlObject.revokeObjectURL(link.href);
|
|
653
|
+
link.remove();
|
|
654
|
+
}, 0);
|
|
655
|
+
}
|
|
656
|
+
function getFileNameFromContentDispositionHeader(header) {
|
|
657
|
+
const result = header.split(";")[1]?.trim().split("=")[1];
|
|
658
|
+
return result?.replace(/^"(.*)"$/, "$1") ?? "";
|
|
659
|
+
}
|
|
660
|
+
|
|
640
661
|
function isPreloaded(targetUrl) {
|
|
641
662
|
const context = getInternalRouterContext();
|
|
642
663
|
return context.preloadCache.has(targetUrl.toString()) ?? false;
|
|
@@ -758,10 +779,7 @@ async function performHybridNavigation(options) {
|
|
|
758
779
|
context.pendingNavigation?.controller?.abort();
|
|
759
780
|
}
|
|
760
781
|
saveScrollPositions();
|
|
761
|
-
|
|
762
|
-
options.url = makeUrl(options.url, options.transformUrl);
|
|
763
|
-
}
|
|
764
|
-
const targetUrl = makeUrl(options.url ?? context.url);
|
|
782
|
+
const targetUrl = makeUrl(options.url ?? context.url, options.transformUrl);
|
|
765
783
|
const abortController = new AbortController();
|
|
766
784
|
setContext({
|
|
767
785
|
pendingNavigation: {
|
|
@@ -775,7 +793,10 @@ async function performHybridNavigation(options) {
|
|
|
775
793
|
await runHooks("start", options.hooks, context);
|
|
776
794
|
debug.router("Making request with axios.");
|
|
777
795
|
const response = await performHybridRequest(targetUrl, options, abortController);
|
|
778
|
-
await runHooks("data", options.hooks, response, context);
|
|
796
|
+
const result = await runHooks("data", options.hooks, response, context);
|
|
797
|
+
if (result === false) {
|
|
798
|
+
return { response };
|
|
799
|
+
}
|
|
779
800
|
if (isExternalResponse(response)) {
|
|
780
801
|
debug.router("The response is explicitely external.");
|
|
781
802
|
await performExternalNavigation({
|
|
@@ -784,6 +805,11 @@ async function performHybridNavigation(options) {
|
|
|
784
805
|
});
|
|
785
806
|
return { response };
|
|
786
807
|
}
|
|
808
|
+
if (isDownloadResponse(response)) {
|
|
809
|
+
debug.router("The response returns a file to download.");
|
|
810
|
+
await handleDownloadResponse(response);
|
|
811
|
+
return { response };
|
|
812
|
+
}
|
|
787
813
|
if (!isHybridResponse(response)) {
|
|
788
814
|
throw new NotAHybridResponseError(response);
|
|
789
815
|
}
|
|
@@ -795,6 +821,7 @@ async function performHybridNavigation(options) {
|
|
|
795
821
|
debug.router("Merged properties:", payload.view.properties);
|
|
796
822
|
}
|
|
797
823
|
await navigate({
|
|
824
|
+
type: "server",
|
|
798
825
|
payload: {
|
|
799
826
|
...payload,
|
|
800
827
|
url: fillHash(targetUrl, payload.url)
|
|
@@ -878,6 +905,7 @@ function isHybridResponse(response) {
|
|
|
878
905
|
}
|
|
879
906
|
async function navigate(options) {
|
|
880
907
|
const context = getRouterContext();
|
|
908
|
+
options.hasDialog ?? (options.hasDialog = !!options.payload?.dialog);
|
|
881
909
|
debug.router("Making an internal navigation:", { context, options });
|
|
882
910
|
await runHooks("navigating", {}, options, context);
|
|
883
911
|
options.payload ?? (options.payload = payloadFromContext());
|
|
@@ -888,6 +916,7 @@ async function navigate(options) {
|
|
|
888
916
|
const shouldPreserveScroll = evaluateConditionalOption(options.preserveScroll);
|
|
889
917
|
const shouldReplaceHistory = evaluateConditionalOption(options.replace);
|
|
890
918
|
const shouldReplaceUrl = evaluateConditionalOption(options.preserveUrl);
|
|
919
|
+
const shouldPreserveView = !options.payload.view.component;
|
|
891
920
|
if (shouldPreserveState && getHistoryMemo() && options.payload.view.component === context.view.component) {
|
|
892
921
|
debug.history("Setting the memo from this history entry into the current context.");
|
|
893
922
|
setContext({ memo: getHistoryMemo() });
|
|
@@ -896,26 +925,44 @@ async function navigate(options) {
|
|
|
896
925
|
debug.router(`Preserving the current URL (${context.url}) instead of navigating to ${options.payload.url}`);
|
|
897
926
|
options.payload.url = context.url;
|
|
898
927
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
928
|
+
const payload = shouldPreserveView ? {
|
|
929
|
+
view: {
|
|
930
|
+
component: context.view.component,
|
|
931
|
+
properties: merge(context.view.properties, options.payload.view.properties),
|
|
932
|
+
deferred: context.view.deferred
|
|
933
|
+
},
|
|
934
|
+
url: context.url,
|
|
935
|
+
version: options.payload.version,
|
|
936
|
+
dialog: context.dialog
|
|
937
|
+
} : options.payload;
|
|
938
|
+
setContext({ ...payload, memo: {} });
|
|
903
939
|
if (options.updateHistoryState !== false) {
|
|
904
940
|
debug.router(`Target URL is ${context.url}, current window URL is ${window.location.href}.`, { shouldReplaceHistory });
|
|
905
941
|
setHistoryState({ replace: shouldReplaceHistory });
|
|
906
942
|
}
|
|
907
|
-
context.
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
943
|
+
if (context.view.deferred?.length) {
|
|
944
|
+
debug.router("Request has deferred properties, queueing a partial reload:", context.view.deferred);
|
|
945
|
+
context.adapter.executeOnMounted(async () => {
|
|
946
|
+
await performHybridNavigation({
|
|
947
|
+
preserveScroll: true,
|
|
948
|
+
preserveState: true,
|
|
949
|
+
replace: true,
|
|
950
|
+
only: context.view.deferred
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
const viewComponent = !shouldPreserveView ? await context.adapter.resolveComponent(context.view.component) : void 0;
|
|
955
|
+
if (viewComponent) {
|
|
956
|
+
debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
|
|
957
|
+
}
|
|
912
958
|
await context.adapter.onViewSwap({
|
|
913
959
|
component: viewComponent,
|
|
914
960
|
dialog: context.dialog,
|
|
915
961
|
properties: options.payload?.view?.properties,
|
|
916
|
-
preserveState: shouldPreserveState
|
|
962
|
+
preserveState: shouldPreserveState,
|
|
963
|
+
onMounted: (hookOptions) => runHooks("mounted", {}, { ...options, ...hookOptions }, context)
|
|
917
964
|
});
|
|
918
|
-
if (options.
|
|
965
|
+
if (options.type === "back-forward") {
|
|
919
966
|
restoreScrollPositions();
|
|
920
967
|
} else if (!shouldPreserveScroll) {
|
|
921
968
|
resetScrollPositions();
|
|
@@ -967,12 +1014,12 @@ async function initializeRouter() {
|
|
|
967
1014
|
} else if (isExternalNavigation()) {
|
|
968
1015
|
handleExternalNavigation();
|
|
969
1016
|
} else {
|
|
970
|
-
debug.router("Handling the initial
|
|
1017
|
+
debug.router("Handling the initial navigation.");
|
|
971
1018
|
setContext({
|
|
972
1019
|
url: makeUrl(context.url, { hash: window.location.hash }).toString()
|
|
973
1020
|
});
|
|
974
1021
|
await navigate({
|
|
975
|
-
|
|
1022
|
+
type: "initial",
|
|
976
1023
|
preserveState: true,
|
|
977
1024
|
replace: sameUrls(context.url, window.location.href)
|
|
978
1025
|
});
|
|
@@ -986,13 +1033,15 @@ async function performLocalNavigation(targetUrl, options) {
|
|
|
986
1033
|
const url = normalizeUrl(targetUrl);
|
|
987
1034
|
return await navigate({
|
|
988
1035
|
...options,
|
|
1036
|
+
type: "local",
|
|
989
1037
|
payload: {
|
|
990
1038
|
version: context.version,
|
|
991
1039
|
dialog: options?.dialog === false ? void 0 : options?.dialog ?? context.dialog,
|
|
992
1040
|
url,
|
|
993
1041
|
view: {
|
|
994
1042
|
component: options?.component ?? context.view.component,
|
|
995
|
-
properties: options?.properties ?? {}
|
|
1043
|
+
properties: options?.properties ?? {},
|
|
1044
|
+
deferred: []
|
|
996
1045
|
}
|
|
997
1046
|
}
|
|
998
1047
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridly/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Core functionality of Hybridly",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hybridly",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"qs": "^6.11.2",
|
|
40
40
|
"superjson": "^1.13.1",
|
|
41
|
-
"@hybridly/utils": "0.
|
|
41
|
+
"@hybridly/utils": "0.5.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"defu": "^6.1.2"
|