@hybridly/core 0.10.0-beta.4 → 0.10.0-beta.6

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.
@@ -1,20 +1,12 @@
1
1
  import "node:module";
2
-
3
- //#region rolldown:runtime
4
2
  var __defProp = Object.defineProperty;
5
- var __exportAll = (all, symbols) => {
3
+ var __exportAll = (all, no_symbols) => {
6
4
  let target = {};
7
- for (var name in all) {
8
- __defProp(target, name, {
9
- get: all[name],
10
- enumerable: true
11
- });
12
- }
13
- if (symbols) {
14
- __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- }
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
10
  return target;
17
11
  };
18
-
19
- //#endregion
20
- export { __exportAll as t };
12
+ export { __exportAll as t };
package/dist/index.mjs CHANGED
@@ -3,8 +3,6 @@ import { debounce, debug, hasFiles, match, merge, objectToFormData, random, remo
3
3
  import axios from "axios";
4
4
  import { parse, stringify } from "superjson";
5
5
  import qs from "qs";
6
-
7
- //#region src/constants.ts
8
6
  var constants_exports = /* @__PURE__ */ __exportAll({
9
7
  DIALOG_KEY_HEADER: () => DIALOG_KEY_HEADER,
10
8
  DIALOG_REDIRECT_HEADER: () => DIALOG_REDIRECT_HEADER,
@@ -31,9 +29,6 @@ const EXCEPT_DATA_HEADER = `${HYBRIDLY_HEADER}-except-data`;
31
29
  const VERSION_HEADER = `${HYBRIDLY_HEADER}-version`;
32
30
  const ERROR_BAG_HEADER = `${HYBRIDLY_HEADER}-error-bag`;
33
31
  const SCROLL_REGION_ATTRIBUTE = "scroll-region";
34
-
35
- //#endregion
36
- //#region src/errors.ts
37
32
  var NotAHybridResponseError = class extends Error {
38
33
  constructor(response) {
39
34
  super();
@@ -56,9 +51,6 @@ var MissingRouteParameter = class extends Error {
56
51
  super(`Parameter [${parameter}] is required for route [${routeName}].`);
57
52
  }
58
53
  };
59
-
60
- //#endregion
61
- //#region src/plugins/plugin.ts
62
54
  function definePlugin(plugin) {
63
55
  return plugin;
64
56
  }
@@ -95,12 +87,6 @@ async function runHooks(hook, requestHooks, ...args) {
95
87
  debug.hook(`Called all hooks for [${hook}],`, result);
96
88
  return !result.includes(false);
97
89
  }
98
-
99
- //#endregion
100
- //#region src/plugins/hooks.ts
101
- /**
102
- * Registers a global hook.
103
- */
104
90
  function appendCallbackToHooks(hook, fn) {
105
91
  const hooks = getRouterContext().hooks;
106
92
  hooks[hook] = [...hooks[hook] ?? [], fn];
@@ -109,9 +95,6 @@ function appendCallbackToHooks(hook, fn) {
109
95
  if (index !== -1) hooks[hook]?.splice(index, 1);
110
96
  };
111
97
  }
112
- /**
113
- * Registers a global hook.
114
- */
115
98
  function registerHook(hook, fn, options) {
116
99
  if (options?.once) {
117
100
  const unregister = appendCallbackToHooks(hook, async (...args) => {
@@ -122,10 +105,6 @@ function registerHook(hook, fn, options) {
122
105
  }
123
106
  return appendCallbackToHooks(hook, fn);
124
107
  }
125
-
126
- //#endregion
127
- //#region src/scroll.ts
128
- /** Saves the current view's scrollbar positions into the history state. */
129
108
  function saveScrollPositions() {
130
109
  const regions = getScrollRegions();
131
110
  debug.scroll("Saving scroll positions of:", regions.map((el) => ({
@@ -141,14 +120,9 @@ function saveScrollPositions() {
141
120
  })) });
142
121
  setHistoryState({ replace: true });
143
122
  }
144
- /** Gets DOM elements which scroll positions should be saved. */
145
123
  function getScrollRegions() {
146
124
  return Array.from(document?.querySelectorAll(`[${SCROLL_REGION_ATTRIBUTE}]`) ?? []).concat(document.documentElement, document.body);
147
125
  }
148
- /**
149
- * Resets the current view's scrollbars positions to the top, and save them
150
- * in the history state.
151
- */
152
126
  function resetScrollPositions() {
153
127
  debug.scroll("Resetting scroll positions.");
154
128
  getScrollRegions().forEach((element) => element.scrollTo({
@@ -161,7 +135,6 @@ function resetScrollPositions() {
161
135
  document.getElementById(window.location.hash.slice(1))?.scrollIntoView();
162
136
  }
163
137
  }
164
- /** Restores the scroll positions stored in the context. */
165
138
  async function restoreScrollPositions() {
166
139
  const context = getRouterContext();
167
140
  const regions = getScrollRegions();
@@ -177,16 +150,9 @@ async function restoreScrollPositions() {
177
150
  }));
178
151
  });
179
152
  }
180
-
181
- //#endregion
182
- //#region src/url.ts
183
- /** Normalizes the given input to an URL. */
184
153
  function normalizeUrl(href, trailingSlash) {
185
154
  return makeUrl(href, { trailingSlash }).toString();
186
155
  }
187
- /**
188
- * Converts an input to an URL, optionally changing its properties after initialization.
189
- */
190
156
  function makeUrl(href, transformations = {}) {
191
157
  try {
192
158
  const base = document?.location?.href === "//" ? void 0 : document.location.href;
@@ -213,9 +179,6 @@ function makeUrl(href, transformations = {}) {
213
179
  throw new TypeError(`${href} is not resolvable to a valid URL.`);
214
180
  }
215
181
  }
216
- /**
217
- * Checks if the given URLs have the same origin and path.
218
- */
219
182
  function sameUrls(...hrefs) {
220
183
  if (hrefs.length < 2) return true;
221
184
  try {
@@ -225,9 +188,6 @@ function sameUrls(...hrefs) {
225
188
  } catch {}
226
189
  return false;
227
190
  }
228
- /**
229
- * Checks if the given URLs have the same origin, path, and hash.
230
- */
231
191
  function sameHashes(...hrefs) {
232
192
  if (hrefs.length < 2) return true;
233
193
  try {
@@ -237,21 +197,12 @@ function sameHashes(...hrefs) {
237
197
  } catch {}
238
198
  return false;
239
199
  }
240
- /**
241
- * If the back-end did not specify a hash, if the navigation specified one,
242
- * and both URLs lead to the same endpoint, we update the target URL
243
- * to use the hash of the initially-requested URL.
244
- */
245
200
  function fillHash(currentUrl, targetUrl) {
246
201
  currentUrl = makeUrl(currentUrl);
247
202
  targetUrl = makeUrl(targetUrl);
248
203
  if (currentUrl.hash && !targetUrl.hash && sameUrls(targetUrl, currentUrl)) targetUrl.hash = currentUrl.hash;
249
204
  return targetUrl.toString();
250
205
  }
251
-
252
- //#endregion
253
- //#region src/router/history.ts
254
- /** Puts the given context into the history state. */
255
206
  function setHistoryState(options = {}) {
256
207
  if (!window?.history) throw new Error("The history API is not available, so Hybridly cannot operate.");
257
208
  const context = getRouterContext();
@@ -268,16 +219,13 @@ function setHistoryState(options = {}) {
268
219
  throw error;
269
220
  }
270
221
  }
271
- /** Gets the current history data if it exists. */
272
222
  function getHistoryState() {
273
223
  return getRouterContext().serializer.unserialize(window.history.state);
274
224
  }
275
- /** Gets the current history state if it exists. */
276
225
  function getHistoryMemo(key) {
277
226
  const state = getHistoryState();
278
227
  return key ? state?.memo?.[key] : state?.memo;
279
228
  }
280
- /** Register history-related event listeneners. */
281
229
  async function registerEventListeners() {
282
230
  const context = getRouterContext();
283
231
  debug.history("Registering [popstate] and [scroll] event listeners.");
@@ -314,12 +262,10 @@ async function registerEventListeners() {
314
262
  if ((event?.target)?.hasAttribute?.(SCROLL_REGION_ATTRIBUTE)) saveScrollPositions();
315
263
  }), true);
316
264
  }
317
- /** Checks if the current navigation was made by going back or forward. */
318
265
  function isBackForwardNavigation() {
319
266
  if (!window.history.state) return false;
320
267
  return (window.performance?.getEntriesByType("navigation").at(0))?.type === "back_forward";
321
268
  }
322
- /** Handles a navigation which was going back or forward. */
323
269
  async function handleBackForwardNavigation() {
324
270
  debug.router("Handling a back/forward navigation from an external URL.");
325
271
  const context = getRouterContext();
@@ -336,7 +282,6 @@ async function handleBackForwardNavigation() {
336
282
  updateHistoryState: false
337
283
  });
338
284
  }
339
- /** Saves a value into the current history state. */
340
285
  function remember(key, value) {
341
286
  debug.history(`Remembering key "${key}" with value`, value);
342
287
  setContext({ memo: {
@@ -345,7 +290,6 @@ function remember(key, value) {
345
290
  } }, { propagate: false });
346
291
  setHistoryState({ replace: true });
347
292
  }
348
- /** Serializes the context so it can be written to the history state. */
349
293
  function serializeContext(context) {
350
294
  return context.serializer.serialize({
351
295
  url: context.url,
@@ -372,9 +316,6 @@ function createSerializer(options) {
372
316
  }
373
317
  };
374
318
  }
375
-
376
- //#endregion
377
- //#region src/routing/route.ts
378
319
  function getUrlRegexForRoute(name) {
379
320
  const routing = getRouting();
380
321
  const definition = getRouteDefinition(name);
@@ -392,12 +333,6 @@ function getUrlRegexForRoute(name) {
392
333
  });
393
334
  return RegExp(urlRegexPattern);
394
335
  }
395
- /**
396
- * Check if a given URL matches a route based on its name.
397
- * Additionally you can pass an object of parameters to check if the URL matches the route with the given parameters.
398
- * Otherwise it will accept and thus return true for any values for the parameters defined by the route.
399
- * Note: passing additional parameters that are not defined by the route or included in the current URL will cause this to return false.
400
- */
401
336
  function urlMatchesRoute(fullUrl, name, routeParameters) {
402
337
  const url = makeUrl(fullUrl, { hash: "" }).toString();
403
338
  const parameters = routeParameters || {};
@@ -434,9 +369,6 @@ function getUrlFromName(name, parameters, shouldThrow) {
434
369
  ...transforms
435
370
  }));
436
371
  }
437
- /**
438
- * Resolved the value of a route parameter from either the passed parameters or the default parameters.
439
- */
440
372
  function getRouteParameterValue(routeName, parameterName, routeParameters) {
441
373
  const routing = getRouting();
442
374
  const definition = getRouteDefinition(routeName);
@@ -454,9 +386,6 @@ function getRouteParameterValue(routeName, parameterName, routeParameters) {
454
386
  }
455
387
  if (routing.defaults?.[parameterName]) return routing.defaults?.[parameterName];
456
388
  }
457
- /**
458
- * Gets the `UrlTransformable` object for the given route and parameters.
459
- */
460
389
  function getRouteTransformable(routeName, routeParameters, shouldThrow) {
461
390
  const definition = getRouteDefinition(routeName);
462
391
  const parameters = routeParameters || {};
@@ -486,45 +415,23 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
486
415
  })
487
416
  };
488
417
  }
489
- /**
490
- * Gets the route definition.
491
- */
492
418
  function getRouteDefinition(name) {
493
419
  const definition = getRouting().routes[name];
494
420
  if (!definition) throw new RouteNotFound(name);
495
421
  return definition;
496
422
  }
497
- /**
498
- * Gets the routing configuration from the current context.
499
- */
500
423
  function getRouting() {
501
424
  const { routing } = getInternalRouterContext();
502
425
  if (!routing) throw new RoutingNotInitialized();
503
426
  return routing;
504
427
  }
505
- /**
506
- * Generates a route from the given route name.
507
- */
508
428
  function route(name, parameters, absolute) {
509
429
  return generateRouteFromName(name, parameters, absolute ?? getRouting().absolute ?? true);
510
430
  }
511
-
512
- //#endregion
513
- //#region src/routing/current.ts
514
431
  function getCurrentUrl() {
515
432
  if (typeof window === "undefined") return getInternalRouterContext().url;
516
433
  return window.location.toString();
517
434
  }
518
- /**
519
- * Determines whether the current route matches the given name and parameters.
520
- * If multiple routes match, the first one will be returned.
521
- *
522
- * @example
523
- * ```ts
524
- * currentRouteMatches('tenant.*') // matches all routes starting with 'tenant.'
525
- * currentRouteMatches('tenant.*.admin') // matches all routes starting with 'tenant.' and ending with '.admin'
526
- * ```
527
- */
528
435
  function currentRouteMatches(name, parameters) {
529
436
  const namePattern = `^${name.replaceAll(".", "\\.").replaceAll("*", ".*")}$`;
530
437
  const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
@@ -536,21 +443,13 @@ function currentRouteMatches(name, parameters) {
536
443
  function getCurrentRouteName() {
537
444
  return getNameFromUrl(getCurrentUrl());
538
445
  }
539
-
540
- //#endregion
541
- //#region src/routing/index.ts
542
446
  function updateRoutingConfiguration(routing) {
543
447
  if (!routing) return;
544
448
  setContext({ routing });
545
449
  }
546
-
547
- //#endregion
548
- //#region src/download.ts
549
- /** Checks if the response wants to redirect to an external URL. */
550
450
  function isDownloadResponse(response) {
551
451
  return response.status === 200 && !!response.headers["content-disposition"];
552
452
  }
553
- /** Handles a download. */
554
453
  async function handleDownloadResponse(response) {
555
454
  const blob = new Blob([response.data], { type: response.headers["content-type"] });
556
455
  const urlObject = window.webkitURL || window.URL;
@@ -567,23 +466,17 @@ async function handleDownloadResponse(response) {
567
466
  function getFileNameFromContentDispositionHeader(header) {
568
467
  return (header.split(";")[1]?.trim().split("=")[1])?.replace(/^"(.*)"$/, "$1") ?? "";
569
468
  }
570
-
571
- //#endregion
572
- //#region src/context/context.ts
573
469
  const state = {
574
470
  initialized: false,
575
471
  context: {}
576
472
  };
577
- /** Gets the current context. */
578
473
  function getRouterContext() {
579
474
  return getInternalRouterContext();
580
475
  }
581
- /** Gets the current context, but not in read-only. */
582
476
  function getInternalRouterContext() {
583
477
  if (!state.initialized) throw new Error("Hybridly is not initialized.");
584
478
  return state.context;
585
479
  }
586
- /** Initializes the context. */
587
480
  async function initializeContext(options) {
588
481
  state.initialized = true;
589
482
  state.context = {
@@ -606,10 +499,6 @@ async function initializeContext(options) {
606
499
  await runHooks("initialized", {}, state.context);
607
500
  return getInternalRouterContext();
608
501
  }
609
- /**
610
- * Registers an interceptor that assumes `arraybuffer`
611
- * responses and converts responses to JSON or text.
612
- */
613
502
  function registerAxios(axios) {
614
503
  axios.interceptors.response.use((response) => {
615
504
  if (!isDownloadResponse(response)) {
@@ -624,9 +513,6 @@ function registerAxios(axios) {
624
513
  }, (error) => Promise.reject(error));
625
514
  return axios;
626
515
  }
627
- /**
628
- * Mutates properties at the top-level of the context.
629
- */
630
516
  function setContext(merge = {}, options = {}) {
631
517
  Object.keys(merge).forEach((key) => {
632
518
  Reflect.set(state.context, key, merge[key]);
@@ -637,7 +523,6 @@ function setContext(merge = {}, options = {}) {
637
523
  added: merge
638
524
  });
639
525
  }
640
- /** Gets a payload from the current context. */
641
526
  function payloadFromContext() {
642
527
  return {
643
528
  url: getRouterContext().url,
@@ -646,14 +531,6 @@ function payloadFromContext() {
646
531
  dialog: getRouterContext().dialog
647
532
  };
648
533
  }
649
-
650
- //#endregion
651
- //#region src/external.ts
652
- /**
653
- * Performs an external navigation by saving options to the storage and
654
- * making a full page reload. Upon loading, the navigation options
655
- * will be pulled and a hybrid navigation will be made.
656
- */
657
534
  async function performExternalNavigation(options) {
658
535
  debug.external("Navigating to an external URL:", options);
659
536
  if (options.target === "new-tab") {
@@ -672,21 +549,15 @@ async function performExternalNavigation(options) {
672
549
  window.location.reload();
673
550
  }
674
551
  }
675
- /** Navigates to the given URL without the hybrid protocol. */
676
552
  function navigateToExternalUrl(url, data) {
677
553
  document.location.href = makeUrl(url, { search: qs.stringify(data, {
678
554
  encodeValuesOnly: true,
679
555
  arrayFormat: "brackets"
680
556
  }) }).toString();
681
557
  }
682
- /** Checks if the response wants to redirect to an external URL. */
683
558
  function isExternalResponse(response) {
684
559
  return response?.status === 409 && !!response?.headers?.[EXTERNAL_NAVIGATION_HEADER];
685
560
  }
686
- /**
687
- * Performs the internal navigation when an external navigation to a hybrid view has been made.
688
- * This method is meant to be called on router creation.
689
- */
690
561
  async function handleExternalNavigation() {
691
562
  debug.external("Handling an external navigation.");
692
563
  const options = JSON.parse(window.sessionStorage.getItem(STORAGE_EXTERNAL_KEY) || "{}");
@@ -699,19 +570,12 @@ async function handleExternalNavigation() {
699
570
  preserveScroll: options.preserveScroll
700
571
  });
701
572
  }
702
- /** Checks if the navigation being initialized points to an external location. */
703
573
  function isExternalNavigation() {
704
574
  try {
705
575
  return window.sessionStorage.getItem(STORAGE_EXTERNAL_KEY) !== null;
706
576
  } catch {}
707
577
  return false;
708
578
  }
709
-
710
- //#endregion
711
- //#region src/dialog/index.ts
712
- /**
713
- * Closes the dialog.
714
- */
715
579
  async function closeDialog(options) {
716
580
  const context = getInternalRouterContext();
717
581
  const url = context.dialog?.redirectUrl ?? context.dialog?.baseUrl;
@@ -732,34 +596,18 @@ async function closeDialog(options) {
732
596
  ...options
733
597
  });
734
598
  }
735
-
736
- //#endregion
737
- //#region src/router/preload.ts
738
- /**
739
- * Checks if there is a preloaded request for the given URL.
740
- */
741
599
  function isPreloaded(targetUrl) {
742
600
  return getInternalRouterContext().preloadCache.has(targetUrl.toString()) ?? false;
743
601
  }
744
- /**
745
- * Gets the response of a preloaded request.
746
- */
747
602
  function getPreloadedRequest(targetUrl) {
748
603
  return getInternalRouterContext().preloadCache.get(targetUrl.toString());
749
604
  }
750
- /**
751
- * Stores the response of a preloaded request.
752
- */
753
605
  function storePreloadRequest(targetUrl, response) {
754
606
  getInternalRouterContext().preloadCache.set(targetUrl.toString(), response);
755
607
  }
756
- /**
757
- * Discards a preloaded request.
758
- */
759
608
  function discardPreloadedRequest(targetUrl) {
760
609
  return getInternalRouterContext().preloadCache.delete(targetUrl.toString());
761
610
  }
762
- /** Preloads a hybrid request. */
763
611
  async function performPreloadRequest(options) {
764
612
  const context = getRouterContext();
765
613
  const url = makeUrl(options.url ?? context.url);
@@ -789,18 +637,6 @@ async function performPreloadRequest(options) {
789
637
  return false;
790
638
  }
791
639
  }
792
-
793
- //#endregion
794
- //#region src/router/router.ts
795
- /**
796
- * The hybridly router.
797
- * This is the core function that you can use to navigate in
798
- * your application. Make sure the routes you call return a
799
- * hybrid response, otherwise you need to call `external`.
800
- *
801
- * @example
802
- * router.get('/posts/edit', { post })
803
- */
804
640
  const router = {
805
641
  abort: async () => getRouterContext().pendingNavigation?.controller.abort(),
806
642
  active: () => !!getRouterContext().pendingNavigation,
@@ -864,12 +700,10 @@ const router = {
864
700
  remember: (key, value) => remember(key, value)
865
701
  }
866
702
  };
867
- /** Creates the hybridly router. */
868
703
  async function createRouter(options) {
869
704
  await initializeContext(options);
870
705
  return await initializeRouter();
871
706
  }
872
- /** Performs every action necessary to make a hybrid navigation. */
873
707
  async function performHybridNavigation(options) {
874
708
  const navigationId = random();
875
709
  const context = getRouterContext();
@@ -1020,14 +854,9 @@ async function performHybridNavigation(options) {
1020
854
  if (context.pendingNavigation?.id === navigationId) setContext({ pendingNavigation: void 0 });
1021
855
  }
1022
856
  }
1023
- /** Checks if the response contains a hybrid header. */
1024
857
  function isHybridResponse(response) {
1025
858
  return !!response?.headers[HYBRIDLY_HEADER];
1026
859
  }
1027
- /**
1028
- * Makes an internal navigation that swaps the view and updates the context.
1029
- * @internal
1030
- */
1031
860
  async function navigate(options) {
1032
861
  const context = getRouterContext();
1033
862
  options.hasDialog ??= !!options.payload?.dialog;
@@ -1137,7 +966,6 @@ async function performHybridRequest(targetUrl, options, abortController) {
1137
966
  }
1138
967
  });
1139
968
  }
1140
- /** Initializes the router by reading the context and registering events if necessary. */
1141
969
  async function initializeRouter() {
1142
970
  const context = getRouterContext();
1143
971
  if (isBackForwardNavigation()) handleBackForwardNavigation();
@@ -1155,7 +983,6 @@ async function initializeRouter() {
1155
983
  await runHooks("ready", {}, context);
1156
984
  return context;
1157
985
  }
1158
- /** Performs a local navigation to the given component without a round-trip. */
1159
986
  async function performLocalNavigation(targetUrl, options) {
1160
987
  const context = getRouterContext();
1161
988
  const url = normalizeUrl(targetUrl);
@@ -1174,16 +1001,7 @@ async function performLocalNavigation(targetUrl, options) {
1174
1001
  }
1175
1002
  });
1176
1003
  }
1177
-
1178
- //#endregion
1179
- //#region src/authorization.ts
1180
- /**
1181
- * Checks whether the given data has the authorization for the given action.
1182
- * If the data object has no authorization definition corresponding to the given action, this method will return `false`.
1183
- */
1184
1004
  function can(resource, action) {
1185
1005
  return resource.authorization?.[action] ?? false;
1186
1006
  }
1187
-
1188
- //#endregion
1189
- export { can, constants_exports as constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
1007
+ export { can, constants_exports as constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hybridly/core",
3
3
  "type": "module",
4
- "version": "0.10.0-beta.4",
4
+ "version": "0.10.0-beta.6",
5
5
  "description": "Core functionality of Hybridly",
6
6
  "author": "Enzo Innocenzi <enzo@innocenzi.dev>",
7
7
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "axios": "^1.7.2"
44
44
  },
45
45
  "dependencies": {
46
- "@hybridly/utils": "0.10.0-beta.4",
46
+ "@hybridly/utils": "0.10.0-beta.6",
47
47
  "qs": "^6.14.0",
48
48
  "superjson": "^2.2.2"
49
49
  },