@hybridly/core 0.4.3 → 0.4.5

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
@@ -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
- preserveScroll: options.preserveScroll,
611
- preserveState: true
609
+ type: "initial",
610
+ preserveState: true,
611
+ preserveScroll: options.preserveScroll
612
612
  });
613
613
  }
614
614
  function isExternalNavigation() {
@@ -802,6 +802,7 @@ async function performHybridNavigation(options) {
802
802
  utils.debug.router("Merged properties:", payload.view.properties);
803
803
  }
804
804
  await navigate({
805
+ type: "server",
805
806
  payload: {
806
807
  ...payload,
807
808
  url: fillHash(targetUrl, payload.url)
@@ -885,6 +886,7 @@ function isHybridResponse(response) {
885
886
  }
886
887
  async function navigate(options) {
887
888
  const context = getRouterContext();
889
+ options.hasDialog ?? (options.hasDialog = !!options.payload?.dialog);
888
890
  utils.debug.router("Making an internal navigation:", { context, options });
889
891
  await runHooks("navigating", {}, options, context);
890
892
  options.payload ?? (options.payload = payloadFromContext());
@@ -895,6 +897,7 @@ async function navigate(options) {
895
897
  const shouldPreserveScroll = evaluateConditionalOption(options.preserveScroll);
896
898
  const shouldReplaceHistory = evaluateConditionalOption(options.replace);
897
899
  const shouldReplaceUrl = evaluateConditionalOption(options.preserveUrl);
900
+ const shouldPreserveView = !options.payload.view.component;
898
901
  if (shouldPreserveState && getHistoryMemo() && options.payload.view.component === context.view.component) {
899
902
  utils.debug.history("Setting the memo from this history entry into the current context.");
900
903
  setContext({ memo: getHistoryMemo() });
@@ -903,31 +906,49 @@ async function navigate(options) {
903
906
  utils.debug.router(`Preserving the current URL (${context.url}) instead of navigating to ${options.payload.url}`);
904
907
  options.payload.url = context.url;
905
908
  }
906
- setContext({
907
- ...options.payload,
908
- memo: {}
909
- });
909
+ const payload = shouldPreserveView ? {
910
+ view: {
911
+ component: context.view.component,
912
+ properties: utils.merge(context.view.properties, options.payload.view.properties),
913
+ deferred: context.view.deferred
914
+ },
915
+ url: context.url,
916
+ version: options.payload.version,
917
+ dialog: context.dialog
918
+ } : options.payload;
919
+ setContext({ ...payload, memo: {} });
910
920
  if (options.updateHistoryState !== false) {
911
921
  utils.debug.router(`Target URL is ${context.url}, current window URL is ${window.location.href}.`, { shouldReplaceHistory });
912
922
  setHistoryState({ replace: shouldReplaceHistory });
913
923
  }
914
- const viewComponent = await context.adapter.resolveComponent(context.view.component);
915
- utils.debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
924
+ if (context.view.deferred?.length) {
925
+ utils.debug.router("Request has deferred properties, queueing a partial reload:", context.view.deferred);
926
+ context.adapter.executeOnMounted(async () => {
927
+ await performHybridNavigation({
928
+ preserveScroll: true,
929
+ preserveState: true,
930
+ replace: true,
931
+ only: context.view.deferred
932
+ });
933
+ });
934
+ }
935
+ const viewComponent = !shouldPreserveView ? await context.adapter.resolveComponent(context.view.component) : void 0;
936
+ if (viewComponent) {
937
+ utils.debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
938
+ }
916
939
  await context.adapter.onViewSwap({
917
940
  component: viewComponent,
918
941
  dialog: context.dialog,
919
942
  properties: options.payload?.view?.properties,
920
- preserveState: shouldPreserveState
943
+ preserveState: shouldPreserveState,
944
+ onMounted: (hookOptions) => runHooks("mounted", {}, { ...options, ...hookOptions }, context)
921
945
  });
922
- if (options.isBackForward) {
946
+ if (options.type === "back-forward") {
923
947
  restoreScrollPositions();
924
948
  } else if (!shouldPreserveScroll) {
925
949
  resetScrollPositions();
926
950
  }
927
951
  await runHooks("navigated", {}, options, context);
928
- context.adapter.executeOnMounted(() => {
929
- runHooks("mounted", {}, context);
930
- });
931
952
  }
932
953
  async function performHybridRequest(targetUrl, options, abortController) {
933
954
  const context = getInternalRouterContext();
@@ -979,6 +1000,7 @@ async function initializeRouter() {
979
1000
  url: makeUrl(context.url, { hash: window.location.hash }).toString()
980
1001
  });
981
1002
  await navigate({
1003
+ type: "initial",
982
1004
  preserveState: true,
983
1005
  replace: sameUrls(context.url, window.location.href)
984
1006
  });
@@ -992,13 +1014,15 @@ async function performLocalNavigation(targetUrl, options) {
992
1014
  const url = normalizeUrl(targetUrl);
993
1015
  return await navigate({
994
1016
  ...options,
1017
+ type: "local",
995
1018
  payload: {
996
1019
  version: context.version,
997
1020
  dialog: options?.dialog === false ? void 0 : options?.dialog ?? context.dialog,
998
1021
  url,
999
1022
  view: {
1000
1023
  component: options?.component ?? context.view.component,
1001
- properties: options?.properties ?? {}
1024
+ properties: options?.properties ?? {},
1025
+ deferred: []
1002
1026
  }
1003
1027
  }
1004
1028
  });
package/dist/index.d.cts CHANGED
@@ -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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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. */
@@ -160,11 +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 whether this navigation is a back/forward navigation from the popstate event.
165
- * @internal This is an advanced property meant to be used internally.
172
+ * Defines the kind of navigation being performed.
173
+ * - initial: the initial page 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
178
+ */
179
+ type: 'initial' | 'local' | 'back-forward' | 'server';
180
+ /**
181
+ * Defines whether this navigation opens a dialog.
182
+ * @internal
166
183
  */
167
- isBackForward?: boolean;
184
+ hasDialog?: boolean;
168
185
  }
169
186
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
170
187
  interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
@@ -264,11 +281,13 @@ interface PendingNavigation {
264
281
  /** A page or dialog component. */
265
282
  interface View {
266
283
  /** Name of the component to use. */
267
- component: string;
284
+ component?: string;
268
285
  /** Properties to apply to the component. */
269
286
  properties: Properties;
287
+ /** Deferred properties for this view. */
288
+ deferred: string[];
270
289
  }
271
- interface Dialog extends View {
290
+ interface Dialog extends Required<View> {
272
291
  /** URL that is the base background page when navigating to the dialog directly. */
273
292
  baseUrl: string;
274
293
  /** URL to which the dialog should redirect when closed. */
@@ -289,6 +308,8 @@ interface SwapOptions<T> {
289
308
  preserveState?: boolean;
290
309
  /** Current dialog. */
291
310
  dialog?: Dialog;
311
+ /** On mounted callback. */
312
+ onMounted?: (options: MountedHookOptions) => void;
292
313
  }
293
314
  type ViewComponent = any;
294
315
  type ResolveComponent = (name: string) => Promise<ViewComponent>;
@@ -466,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
466
487
  declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
467
488
  declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
468
489
  declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
469
- declare const CONTEXT_HEADER = "x-hybrid-context";
470
490
  declare const VERSION_HEADER = "x-hybrid-version";
471
491
  declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
472
492
  declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
473
493
 
474
- declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
475
494
  declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
476
495
  declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
477
496
  declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
@@ -484,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
484
503
  declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
485
504
  declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
486
505
  declare namespace constants {
487
- export { constants_CONTEXT_HEADER as CONTEXT_HEADER, 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 };
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 };
488
507
  }
489
508
 
490
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
@@ -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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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. */
@@ -160,11 +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 whether this navigation is a back/forward navigation from the popstate event.
165
- * @internal This is an advanced property meant to be used internally.
172
+ * Defines the kind of navigation being performed.
173
+ * - initial: the initial page 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
178
+ */
179
+ type: 'initial' | 'local' | 'back-forward' | 'server';
180
+ /**
181
+ * Defines whether this navigation opens a dialog.
182
+ * @internal
166
183
  */
167
- isBackForward?: boolean;
184
+ hasDialog?: boolean;
168
185
  }
169
186
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
170
187
  interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
@@ -264,11 +281,13 @@ interface PendingNavigation {
264
281
  /** A page or dialog component. */
265
282
  interface View {
266
283
  /** Name of the component to use. */
267
- component: string;
284
+ component?: string;
268
285
  /** Properties to apply to the component. */
269
286
  properties: Properties;
287
+ /** Deferred properties for this view. */
288
+ deferred: string[];
270
289
  }
271
- interface Dialog extends View {
290
+ interface Dialog extends Required<View> {
272
291
  /** URL that is the base background page when navigating to the dialog directly. */
273
292
  baseUrl: string;
274
293
  /** URL to which the dialog should redirect when closed. */
@@ -289,6 +308,8 @@ interface SwapOptions<T> {
289
308
  preserveState?: boolean;
290
309
  /** Current dialog. */
291
310
  dialog?: Dialog;
311
+ /** On mounted callback. */
312
+ onMounted?: (options: MountedHookOptions) => void;
292
313
  }
293
314
  type ViewComponent = any;
294
315
  type ResolveComponent = (name: string) => Promise<ViewComponent>;
@@ -466,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
466
487
  declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
467
488
  declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
468
489
  declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
469
- declare const CONTEXT_HEADER = "x-hybrid-context";
470
490
  declare const VERSION_HEADER = "x-hybrid-version";
471
491
  declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
472
492
  declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
473
493
 
474
- declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
475
494
  declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
476
495
  declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
477
496
  declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
@@ -484,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
484
503
  declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
485
504
  declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
486
505
  declare namespace constants {
487
- export { constants_CONTEXT_HEADER as CONTEXT_HEADER, 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 };
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 };
488
507
  }
489
508
 
490
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
@@ -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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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: NavigationOptions, context: InternalRouterContext) => MaybePromise<any>;
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. */
@@ -160,11 +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 whether this navigation is a back/forward navigation from the popstate event.
165
- * @internal This is an advanced property meant to be used internally.
172
+ * Defines the kind of navigation being performed.
173
+ * - initial: the initial page 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
178
+ */
179
+ type: 'initial' | 'local' | 'back-forward' | 'server';
180
+ /**
181
+ * Defines whether this navigation opens a dialog.
182
+ * @internal
166
183
  */
167
- isBackForward?: boolean;
184
+ hasDialog?: boolean;
168
185
  }
169
186
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
170
187
  interface HybridRequestOptions extends Omit<NavigationOptions, 'payload'> {
@@ -264,11 +281,13 @@ interface PendingNavigation {
264
281
  /** A page or dialog component. */
265
282
  interface View {
266
283
  /** Name of the component to use. */
267
- component: string;
284
+ component?: string;
268
285
  /** Properties to apply to the component. */
269
286
  properties: Properties;
287
+ /** Deferred properties for this view. */
288
+ deferred: string[];
270
289
  }
271
- interface Dialog extends View {
290
+ interface Dialog extends Required<View> {
272
291
  /** URL that is the base background page when navigating to the dialog directly. */
273
292
  baseUrl: string;
274
293
  /** URL to which the dialog should redirect when closed. */
@@ -289,6 +308,8 @@ interface SwapOptions<T> {
289
308
  preserveState?: boolean;
290
309
  /** Current dialog. */
291
310
  dialog?: Dialog;
311
+ /** On mounted callback. */
312
+ onMounted?: (options: MountedHookOptions) => void;
292
313
  }
293
314
  type ViewComponent = any;
294
315
  type ResolveComponent = (name: string) => Promise<ViewComponent>;
@@ -466,12 +487,10 @@ declare const ONLY_DATA_HEADER = "x-hybrid-only-data";
466
487
  declare const DIALOG_KEY_HEADER = "x-hybrid-dialog-key";
467
488
  declare const DIALOG_REDIRECT_HEADER = "x-hybrid-dialog-redirect";
468
489
  declare const EXCEPT_DATA_HEADER = "x-hybrid-except-data";
469
- declare const CONTEXT_HEADER = "x-hybrid-context";
470
490
  declare const VERSION_HEADER = "x-hybrid-version";
471
491
  declare const ERROR_BAG_HEADER = "x-hybrid-error-bag";
472
492
  declare const SCROLL_REGION_ATTRIBUTE = "scroll-region";
473
493
 
474
- declare const constants_CONTEXT_HEADER: typeof CONTEXT_HEADER;
475
494
  declare const constants_DIALOG_KEY_HEADER: typeof DIALOG_KEY_HEADER;
476
495
  declare const constants_DIALOG_REDIRECT_HEADER: typeof DIALOG_REDIRECT_HEADER;
477
496
  declare const constants_ERROR_BAG_HEADER: typeof ERROR_BAG_HEADER;
@@ -484,7 +503,7 @@ declare const constants_SCROLL_REGION_ATTRIBUTE: typeof SCROLL_REGION_ATTRIBUTE;
484
503
  declare const constants_STORAGE_EXTERNAL_KEY: typeof STORAGE_EXTERNAL_KEY;
485
504
  declare const constants_VERSION_HEADER: typeof VERSION_HEADER;
486
505
  declare namespace constants {
487
- export { constants_CONTEXT_HEADER as CONTEXT_HEADER, 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 };
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 };
488
507
  }
489
508
 
490
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
- preserveScroll: options.preserveScroll,
604
- preserveState: true
602
+ type: "initial",
603
+ preserveState: true,
604
+ preserveScroll: options.preserveScroll
605
605
  });
606
606
  }
607
607
  function isExternalNavigation() {
@@ -795,6 +795,7 @@ async function performHybridNavigation(options) {
795
795
  debug.router("Merged properties:", payload.view.properties);
796
796
  }
797
797
  await navigate({
798
+ type: "server",
798
799
  payload: {
799
800
  ...payload,
800
801
  url: fillHash(targetUrl, payload.url)
@@ -878,6 +879,7 @@ function isHybridResponse(response) {
878
879
  }
879
880
  async function navigate(options) {
880
881
  const context = getRouterContext();
882
+ options.hasDialog ?? (options.hasDialog = !!options.payload?.dialog);
881
883
  debug.router("Making an internal navigation:", { context, options });
882
884
  await runHooks("navigating", {}, options, context);
883
885
  options.payload ?? (options.payload = payloadFromContext());
@@ -888,6 +890,7 @@ async function navigate(options) {
888
890
  const shouldPreserveScroll = evaluateConditionalOption(options.preserveScroll);
889
891
  const shouldReplaceHistory = evaluateConditionalOption(options.replace);
890
892
  const shouldReplaceUrl = evaluateConditionalOption(options.preserveUrl);
893
+ const shouldPreserveView = !options.payload.view.component;
891
894
  if (shouldPreserveState && getHistoryMemo() && options.payload.view.component === context.view.component) {
892
895
  debug.history("Setting the memo from this history entry into the current context.");
893
896
  setContext({ memo: getHistoryMemo() });
@@ -896,31 +899,49 @@ async function navigate(options) {
896
899
  debug.router(`Preserving the current URL (${context.url}) instead of navigating to ${options.payload.url}`);
897
900
  options.payload.url = context.url;
898
901
  }
899
- setContext({
900
- ...options.payload,
901
- memo: {}
902
- });
902
+ const payload = shouldPreserveView ? {
903
+ view: {
904
+ component: context.view.component,
905
+ properties: merge(context.view.properties, options.payload.view.properties),
906
+ deferred: context.view.deferred
907
+ },
908
+ url: context.url,
909
+ version: options.payload.version,
910
+ dialog: context.dialog
911
+ } : options.payload;
912
+ setContext({ ...payload, memo: {} });
903
913
  if (options.updateHistoryState !== false) {
904
914
  debug.router(`Target URL is ${context.url}, current window URL is ${window.location.href}.`, { shouldReplaceHistory });
905
915
  setHistoryState({ replace: shouldReplaceHistory });
906
916
  }
907
- const viewComponent = await context.adapter.resolveComponent(context.view.component);
908
- debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
917
+ if (context.view.deferred?.length) {
918
+ debug.router("Request has deferred properties, queueing a partial reload:", context.view.deferred);
919
+ context.adapter.executeOnMounted(async () => {
920
+ await performHybridNavigation({
921
+ preserveScroll: true,
922
+ preserveState: true,
923
+ replace: true,
924
+ only: context.view.deferred
925
+ });
926
+ });
927
+ }
928
+ const viewComponent = !shouldPreserveView ? await context.adapter.resolveComponent(context.view.component) : void 0;
929
+ if (viewComponent) {
930
+ debug.router(`Component [${context.view.component}] resolved to:`, viewComponent);
931
+ }
909
932
  await context.adapter.onViewSwap({
910
933
  component: viewComponent,
911
934
  dialog: context.dialog,
912
935
  properties: options.payload?.view?.properties,
913
- preserveState: shouldPreserveState
936
+ preserveState: shouldPreserveState,
937
+ onMounted: (hookOptions) => runHooks("mounted", {}, { ...options, ...hookOptions }, context)
914
938
  });
915
- if (options.isBackForward) {
939
+ if (options.type === "back-forward") {
916
940
  restoreScrollPositions();
917
941
  } else if (!shouldPreserveScroll) {
918
942
  resetScrollPositions();
919
943
  }
920
944
  await runHooks("navigated", {}, options, context);
921
- context.adapter.executeOnMounted(() => {
922
- runHooks("mounted", {}, context);
923
- });
924
945
  }
925
946
  async function performHybridRequest(targetUrl, options, abortController) {
926
947
  const context = getInternalRouterContext();
@@ -972,6 +993,7 @@ async function initializeRouter() {
972
993
  url: makeUrl(context.url, { hash: window.location.hash }).toString()
973
994
  });
974
995
  await navigate({
996
+ type: "initial",
975
997
  preserveState: true,
976
998
  replace: sameUrls(context.url, window.location.href)
977
999
  });
@@ -985,13 +1007,15 @@ async function performLocalNavigation(targetUrl, options) {
985
1007
  const url = normalizeUrl(targetUrl);
986
1008
  return await navigate({
987
1009
  ...options,
1010
+ type: "local",
988
1011
  payload: {
989
1012
  version: context.version,
990
1013
  dialog: options?.dialog === false ? void 0 : options?.dialog ?? context.dialog,
991
1014
  url,
992
1015
  view: {
993
1016
  component: options?.component ?? context.view.component,
994
- properties: options?.properties ?? {}
1017
+ properties: options?.properties ?? {},
1018
+ deferred: []
995
1019
  }
996
1020
  }
997
1021
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridly/core",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
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.3"
41
+ "@hybridly/utils": "0.4.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "defu": "^6.1.2"