@hybridly/core 0.4.5 → 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 CHANGED
@@ -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
- if (options.url && options.transformUrl) {
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
  }
@@ -995,7 +1021,7 @@ async function initializeRouter() {
995
1021
  } else if (isExternalNavigation()) {
996
1022
  handleExternalNavigation();
997
1023
  } else {
998
- utils.debug.router("Handling the initial page navigation.");
1024
+ utils.debug.router("Handling the initial navigation.");
999
1025
  setContext({
1000
1026
  url: makeUrl(context.url, { hash: window.location.hash }).toString()
1001
1027
  });
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 page load.
58
+ * Called after Hybridly's initial load.
59
59
  */
60
60
  ready: (context: InternalRouterContext) => MaybePromise<any>;
61
61
  /**
@@ -134,7 +134,7 @@ interface ComponentNavigationOptions {
134
134
  replace?: ConditionalNavigationOption<boolean>;
135
135
  /** Whether to preserve the current scrollbar position. */
136
136
  preserveScroll?: ConditionalNavigationOption<boolean>;
137
- /** Whether to preserve the current page component state. */
137
+ /** Whether to preserve the current view component state. */
138
138
  preserveState?: ConditionalNavigationOption<boolean>;
139
139
  }
140
140
  interface NavigationOptions {
@@ -145,9 +145,9 @@ interface NavigationOptions {
145
145
  * one. This affects the browser's "back" and "forward" features.
146
146
  */
147
147
  replace?: ConditionalNavigationOption<boolean>;
148
- /** Whether to preserve the scrollbars positions on the page. */
148
+ /** Whether to preserve the scrollbars positions on the view. */
149
149
  preserveScroll?: ConditionalNavigationOption<boolean>;
150
- /** Whether to preserve the current page component's state. */
150
+ /** Whether to preserve the current view component's state. */
151
151
  preserveState?: ConditionalNavigationOption<boolean>;
152
152
  /** Whether to preserve the current URL. */
153
153
  preserveUrl?: ConditionalNavigationOption<boolean>;
@@ -170,7 +170,7 @@ interface NavigationOptions {
170
170
  interface InternalNavigationOptions extends NavigationOptions {
171
171
  /**
172
172
  * Defines the kind of navigation being performed.
173
- * - initial: the initial page load's navigation
173
+ * - initial: the initial load's navigation
174
174
  * - server: a navigation initiated by a server round-trip
175
175
  * - local: a navigation initiated by `router.local`
176
176
  * - back-forward: a navigation initiated by the browser's `popstate` event
@@ -278,7 +278,7 @@ interface PendingNavigation {
278
278
  /** Current status. */
279
279
  status: 'pending' | 'success' | 'error';
280
280
  }
281
- /** A page or dialog component. */
281
+ /** A view or dialog component. */
282
282
  interface View {
283
283
  /** Name of the component to use. */
284
284
  component?: string;
@@ -288,7 +288,7 @@ interface View {
288
288
  deferred: string[];
289
289
  }
290
290
  interface Dialog extends Required<View> {
291
- /** URL that is the base background page when navigating to the dialog directly. */
291
+ /** URL that is the base background view when navigating to the dialog directly. */
292
292
  baseUrl: string;
293
293
  /** URL to which the dialog should redirect when closed. */
294
294
  redirectUrl: string;
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 page load.
58
+ * Called after Hybridly's initial load.
59
59
  */
60
60
  ready: (context: InternalRouterContext) => MaybePromise<any>;
61
61
  /**
@@ -134,7 +134,7 @@ interface ComponentNavigationOptions {
134
134
  replace?: ConditionalNavigationOption<boolean>;
135
135
  /** Whether to preserve the current scrollbar position. */
136
136
  preserveScroll?: ConditionalNavigationOption<boolean>;
137
- /** Whether to preserve the current page component state. */
137
+ /** Whether to preserve the current view component state. */
138
138
  preserveState?: ConditionalNavigationOption<boolean>;
139
139
  }
140
140
  interface NavigationOptions {
@@ -145,9 +145,9 @@ interface NavigationOptions {
145
145
  * one. This affects the browser's "back" and "forward" features.
146
146
  */
147
147
  replace?: ConditionalNavigationOption<boolean>;
148
- /** Whether to preserve the scrollbars positions on the page. */
148
+ /** Whether to preserve the scrollbars positions on the view. */
149
149
  preserveScroll?: ConditionalNavigationOption<boolean>;
150
- /** Whether to preserve the current page component's state. */
150
+ /** Whether to preserve the current view component's state. */
151
151
  preserveState?: ConditionalNavigationOption<boolean>;
152
152
  /** Whether to preserve the current URL. */
153
153
  preserveUrl?: ConditionalNavigationOption<boolean>;
@@ -170,7 +170,7 @@ interface NavigationOptions {
170
170
  interface InternalNavigationOptions extends NavigationOptions {
171
171
  /**
172
172
  * Defines the kind of navigation being performed.
173
- * - initial: the initial page load's navigation
173
+ * - initial: the initial load's navigation
174
174
  * - server: a navigation initiated by a server round-trip
175
175
  * - local: a navigation initiated by `router.local`
176
176
  * - back-forward: a navigation initiated by the browser's `popstate` event
@@ -278,7 +278,7 @@ interface PendingNavigation {
278
278
  /** Current status. */
279
279
  status: 'pending' | 'success' | 'error';
280
280
  }
281
- /** A page or dialog component. */
281
+ /** A view or dialog component. */
282
282
  interface View {
283
283
  /** Name of the component to use. */
284
284
  component?: string;
@@ -288,7 +288,7 @@ interface View {
288
288
  deferred: string[];
289
289
  }
290
290
  interface Dialog extends Required<View> {
291
- /** URL that is the base background page when navigating to the dialog directly. */
291
+ /** URL that is the base background view when navigating to the dialog directly. */
292
292
  baseUrl: string;
293
293
  /** URL to which the dialog should redirect when closed. */
294
294
  redirectUrl: string;
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 page load.
58
+ * Called after Hybridly's initial load.
59
59
  */
60
60
  ready: (context: InternalRouterContext) => MaybePromise<any>;
61
61
  /**
@@ -134,7 +134,7 @@ interface ComponentNavigationOptions {
134
134
  replace?: ConditionalNavigationOption<boolean>;
135
135
  /** Whether to preserve the current scrollbar position. */
136
136
  preserveScroll?: ConditionalNavigationOption<boolean>;
137
- /** Whether to preserve the current page component state. */
137
+ /** Whether to preserve the current view component state. */
138
138
  preserveState?: ConditionalNavigationOption<boolean>;
139
139
  }
140
140
  interface NavigationOptions {
@@ -145,9 +145,9 @@ interface NavigationOptions {
145
145
  * one. This affects the browser's "back" and "forward" features.
146
146
  */
147
147
  replace?: ConditionalNavigationOption<boolean>;
148
- /** Whether to preserve the scrollbars positions on the page. */
148
+ /** Whether to preserve the scrollbars positions on the view. */
149
149
  preserveScroll?: ConditionalNavigationOption<boolean>;
150
- /** Whether to preserve the current page component's state. */
150
+ /** Whether to preserve the current view component's state. */
151
151
  preserveState?: ConditionalNavigationOption<boolean>;
152
152
  /** Whether to preserve the current URL. */
153
153
  preserveUrl?: ConditionalNavigationOption<boolean>;
@@ -170,7 +170,7 @@ interface NavigationOptions {
170
170
  interface InternalNavigationOptions extends NavigationOptions {
171
171
  /**
172
172
  * Defines the kind of navigation being performed.
173
- * - initial: the initial page load's navigation
173
+ * - initial: the initial load's navigation
174
174
  * - server: a navigation initiated by a server round-trip
175
175
  * - local: a navigation initiated by `router.local`
176
176
  * - back-forward: a navigation initiated by the browser's `popstate` event
@@ -278,7 +278,7 @@ interface PendingNavigation {
278
278
  /** Current status. */
279
279
  status: 'pending' | 'success' | 'error';
280
280
  }
281
- /** A page or dialog component. */
281
+ /** A view or dialog component. */
282
282
  interface View {
283
283
  /** Name of the component to use. */
284
284
  component?: string;
@@ -288,7 +288,7 @@ interface View {
288
288
  deferred: string[];
289
289
  }
290
290
  interface Dialog extends Required<View> {
291
- /** URL that is the base background page when navigating to the dialog directly. */
291
+ /** URL that is the base background view when navigating to the dialog directly. */
292
292
  baseUrl: string;
293
293
  /** URL to which the dialog should redirect when closed. */
294
294
  redirectUrl: string;
package/dist/index.mjs CHANGED
@@ -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
- if (options.url && options.transformUrl) {
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
  }
@@ -988,7 +1014,7 @@ async function initializeRouter() {
988
1014
  } else if (isExternalNavigation()) {
989
1015
  handleExternalNavigation();
990
1016
  } else {
991
- debug.router("Handling the initial page navigation.");
1017
+ debug.router("Handling the initial navigation.");
992
1018
  setContext({
993
1019
  url: makeUrl(context.url, { hash: window.location.hash }).toString()
994
1020
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridly/core",
3
- "version": "0.4.5",
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.4.5"
41
+ "@hybridly/utils": "0.5.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "defu": "^6.1.2"