@hybridly/core 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -360,10 +360,59 @@ function createSerializer(options) {
360
360
  };
361
361
  }
362
362
 
363
+ function getUrlRegexForRoute(name) {
364
+ const routing = getRouting();
365
+ const definition = getRouteDefinition(name);
366
+ const path = definition.uri.replaceAll("/", "\\/");
367
+ const domain = definition.domain;
368
+ const protocolPrefix = routing.url.match(/^\w+:\/\//)?.[0];
369
+ const origin = domain ? `${protocolPrefix}${domain}${routing.port ? `:${routing.port}` : ""}`.replaceAll("/", "\\/") : routing.url.replaceAll("/", "\\/");
370
+ const urlPathRegexPattern = path.length > 0 ? `\\/${path.replace(/\/$/g, "")}` : "";
371
+ let urlRegexPattern = `^${origin.replaceAll(".", "\\.")}${urlPathRegexPattern}\\/?(\\?.*)?$`;
372
+ urlRegexPattern = urlRegexPattern.replace(/(\\\/?){([^}?]+)(\??)}/g, (_, slash, parameterName, optional) => {
373
+ const where = definition.wheres?.[parameterName];
374
+ let regexTemplate = where?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+";
375
+ regexTemplate = `(?<${parameterName}>${regexTemplate})`;
376
+ if (optional) {
377
+ return `(${slash ? "\\/?" : ""}${regexTemplate})?`;
378
+ }
379
+ return (slash ? "\\/" : "") + regexTemplate;
380
+ });
381
+ return RegExp(urlRegexPattern);
382
+ }
383
+ function urlMatchesRoute(url, name, routeParameters) {
384
+ const parameters = routeParameters || {};
385
+ const definition = getRouting().routes[name];
386
+ if (!definition) {
387
+ return false;
388
+ }
389
+ const matches = getUrlRegexForRoute(name).exec(url);
390
+ if (!matches) {
391
+ return false;
392
+ }
393
+ for (const k in matches.groups) {
394
+ matches.groups[k] = typeof matches.groups[k] === "string" ? decodeURIComponent(matches.groups[k]) : matches.groups[k];
395
+ }
396
+ return Object.keys(parameters).every((parameterName) => {
397
+ let value = parameters[parameterName];
398
+ const bindingProperty = definition.bindings?.[parameterName];
399
+ if (bindingProperty && typeof value === "object") {
400
+ value = value[bindingProperty];
401
+ }
402
+ return matches.groups?.[parameterName] === value.toString();
403
+ });
404
+ }
363
405
  function generateRouteFromName(name, parameters, absolute, shouldThrow) {
364
406
  const url = getUrlFromName(name, parameters, shouldThrow);
365
407
  return absolute === false ? url.toString().replace(url.origin, "") : url.toString();
366
408
  }
409
+ function getNameFromUrl(url, parameters) {
410
+ const routing = getRouting();
411
+ const routes = Object.values(routing.routes).map((x) => x.name);
412
+ return routes.find((routeName) => {
413
+ return urlMatchesRoute(url, routeName, parameters);
414
+ });
415
+ }
367
416
  function getUrlFromName(name, parameters, shouldThrow) {
368
417
  const routing = getRouting();
369
418
  const definition = getRouteDefinition(name);
@@ -376,32 +425,39 @@ function getUrlFromName(name, parameters, shouldThrow) {
376
425
  }));
377
426
  return url;
378
427
  }
379
- function getRouteTransformable(routeName, routeParameters, shouldThrow) {
428
+ function getRouteParameterValue(routeName, parameterName, routeParameters) {
380
429
  const routing = getRouting();
430
+ const definition = getRouteDefinition(routeName);
431
+ const parameters = routeParameters || {};
432
+ const value = (() => {
433
+ const value2 = parameters[parameterName];
434
+ const bindingProperty = definition.bindings?.[parameterName];
435
+ if (bindingProperty && value2 != null && typeof value2 === "object") {
436
+ return value2[bindingProperty];
437
+ }
438
+ return value2;
439
+ })();
440
+ if (value) {
441
+ const where = definition.wheres?.[parameterName];
442
+ if (where && !new RegExp(where).test(value)) {
443
+ console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
444
+ }
445
+ return value;
446
+ }
447
+ if (routing.defaults?.[parameterName]) {
448
+ return routing.defaults?.[parameterName];
449
+ }
450
+ }
451
+ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
381
452
  const definition = getRouteDefinition(routeName);
382
453
  const parameters = routeParameters || {};
383
454
  const missing = Object.keys(parameters);
384
- const replaceParameter = (match, parameterName) => {
385
- const optional = /\?}$/.test(match);
386
- const value = (() => {
387
- const value2 = parameters[parameterName];
388
- const bindingProperty = definition.bindings?.[parameterName];
389
- if (bindingProperty && typeof value2 === "object") {
390
- return value2[bindingProperty];
391
- }
392
- return value2;
393
- })();
455
+ const replaceParameter = (match, parameterName, optional) => {
456
+ const value = getRouteParameterValue(routeName, parameterName, parameters);
394
457
  missing.splice(missing.indexOf(parameterName), 1);
395
458
  if (value) {
396
- const where = definition.wheres?.[parameterName];
397
- if (where && !new RegExp(where).test(value)) {
398
- console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
399
- }
400
459
  return value;
401
460
  }
402
- if (routing.defaults?.[parameterName]) {
403
- return routing.defaults?.[parameterName];
404
- }
405
461
  if (optional) {
406
462
  return "";
407
463
  }
@@ -410,8 +466,8 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
410
466
  }
411
467
  throw new MissingRouteParameter(parameterName, routeName);
412
468
  };
413
- const path = definition.uri.replace(/{([^}?]+)\??}/g, replaceParameter);
414
- const domain = definition.domain?.replace(/{([^}?]+)\??}/g, replaceParameter);
469
+ const path = definition.uri.replace(/{([^}?]+)(\??)}/g, replaceParameter);
470
+ const domain = definition.domain?.replace(/{([^}?]+)(\??)}/g, replaceParameter);
415
471
  const remaining = Object.keys(parameters).filter((key) => missing.includes(key)).reduce((obj, key) => ({
416
472
  ...obj,
417
473
  [key]: parameters[key]
@@ -441,30 +497,28 @@ function getRouting() {
441
497
  }
442
498
  return routing;
443
499
  }
444
-
445
- function isCurrentFromName(name, parameters, mode = "loose") {
446
- const location = window.location;
447
- const matchee = (() => {
448
- try {
449
- return makeUrl(generateRouteFromName(name, parameters, true, false));
450
- } catch (error) {
451
- }
452
- })();
453
- if (!matchee) {
454
- return false;
455
- }
456
- if (mode === "strict") {
457
- return location.href === matchee.href;
458
- }
459
- return location.href.startsWith(matchee.href);
460
- }
461
-
462
500
  function route(name, parameters, absolute) {
463
501
  return generateRouteFromName(name, parameters, absolute);
464
502
  }
465
- function current(name, parameters, mode = "loose") {
466
- return isCurrentFromName(name, parameters, mode);
503
+
504
+ function getCurrentUrl() {
505
+ if (typeof window === "undefined") {
506
+ return getInternalRouterContext().url;
507
+ }
508
+ return window.location.toString();
509
+ }
510
+ function currentRouteMatches(name, parameters) {
511
+ const namePattern = name.replaceAll(".", "\\.").replaceAll("*", ".*");
512
+ const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
513
+ const currentUrl = getCurrentUrl();
514
+ return possibleRoutes.some((routeName) => {
515
+ return urlMatchesRoute(currentUrl, routeName, parameters);
516
+ });
467
517
  }
518
+ function getCurrentRouteName() {
519
+ return getNameFromUrl(getCurrentUrl());
520
+ }
521
+
468
522
  function updateRoutingConfiguration(routing) {
469
523
  if (!routing) {
470
524
  return;
@@ -655,6 +709,8 @@ const router = {
655
709
  const method = getRouteDefinition(name).method.at(0);
656
710
  return await performHybridNavigation({ url, ...options, method });
657
711
  },
712
+ matches: (name, parameters) => currentRouteMatches(name, parameters),
713
+ current: () => getCurrentRouteName(),
658
714
  dialog: {
659
715
  close: (options) => closeDialog(options)
660
716
  },
@@ -956,7 +1012,6 @@ function can(resource, action) {
956
1012
  exports.can = can;
957
1013
  exports.constants = constants;
958
1014
  exports.createRouter = createRouter;
959
- exports.current = current;
960
1015
  exports.definePlugin = definePlugin;
961
1016
  exports.getRouterContext = getRouterContext;
962
1017
  exports.makeUrl = makeUrl;
package/dist/index.d.ts CHANGED
@@ -234,6 +234,10 @@ interface Router {
234
234
  local: (url: UrlResolvable, options: ComponentNavigationOptions) => Promise<void>;
235
235
  /** Preloads the given URL. The next time this URL is navigated to, it will be loaded from the cache. */
236
236
  preload: (url: UrlResolvable, options?: Omit<HybridRequestOptions, 'method' | 'url'>) => Promise<boolean>;
237
+ /** Determines if the given route name and parameters matches the current route. */
238
+ matches: <T extends RouteName>(name: T, parameters?: RouteParameters<T>) => boolean;
239
+ /** Gets the current route name. Returns `undefined` is unknown. */
240
+ current: () => string | undefined;
237
241
  /** Access the dialog router. */
238
242
  dialog: DialogRouter;
239
243
  /** Access the history state. */
@@ -434,10 +438,6 @@ declare function can<Authorizations extends Record<string, boolean>, Data extend
434
438
  * Generates a route from the given route name.
435
439
  */
436
440
  declare function route<T extends RouteName>(name: T, parameters?: RouteParameters<T>, absolute?: boolean): string;
437
- /**
438
- * Determines if the current route correspond to the given route name and parameters.
439
- */
440
- declare function current<T extends RouteName>(name: T, parameters?: RouteParameters<T>, mode?: 'loose' | 'strict'): boolean;
441
441
 
442
442
  interface DynamicConfiguration {
443
443
  architecture: {
@@ -500,4 +500,4 @@ declare namespace constants {
500
500
  };
501
501
  }
502
502
 
503
- export { Authorizable, DynamicConfiguration, GlobalRouteCollection, HybridPayload, HybridRequestOptions, MaybePromise, Method, NavigationResponse, Plugin, Progress, ResolveComponent, RouteDefinition, RouteName, RouteParameters, Router, RouterContext, RouterContextOptions, RoutingConfiguration, UrlResolvable, can, constants, createRouter, current, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
503
+ export { Authorizable, DynamicConfiguration, GlobalRouteCollection, HybridPayload, HybridRequestOptions, MaybePromise, Method, NavigationResponse, Plugin, Progress, ResolveComponent, RouteDefinition, RouteName, RouteParameters, Router, RouterContext, RouterContextOptions, RoutingConfiguration, UrlResolvable, can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
package/dist/index.mjs CHANGED
@@ -351,10 +351,59 @@ function createSerializer(options) {
351
351
  };
352
352
  }
353
353
 
354
+ function getUrlRegexForRoute(name) {
355
+ const routing = getRouting();
356
+ const definition = getRouteDefinition(name);
357
+ const path = definition.uri.replaceAll("/", "\\/");
358
+ const domain = definition.domain;
359
+ const protocolPrefix = routing.url.match(/^\w+:\/\//)?.[0];
360
+ const origin = domain ? `${protocolPrefix}${domain}${routing.port ? `:${routing.port}` : ""}`.replaceAll("/", "\\/") : routing.url.replaceAll("/", "\\/");
361
+ const urlPathRegexPattern = path.length > 0 ? `\\/${path.replace(/\/$/g, "")}` : "";
362
+ let urlRegexPattern = `^${origin.replaceAll(".", "\\.")}${urlPathRegexPattern}\\/?(\\?.*)?$`;
363
+ urlRegexPattern = urlRegexPattern.replace(/(\\\/?){([^}?]+)(\??)}/g, (_, slash, parameterName, optional) => {
364
+ const where = definition.wheres?.[parameterName];
365
+ let regexTemplate = where?.replace(/(^\^)|(\$$)/g, "") || "[^/?]+";
366
+ regexTemplate = `(?<${parameterName}>${regexTemplate})`;
367
+ if (optional) {
368
+ return `(${slash ? "\\/?" : ""}${regexTemplate})?`;
369
+ }
370
+ return (slash ? "\\/" : "") + regexTemplate;
371
+ });
372
+ return RegExp(urlRegexPattern);
373
+ }
374
+ function urlMatchesRoute(url, name, routeParameters) {
375
+ const parameters = routeParameters || {};
376
+ const definition = getRouting().routes[name];
377
+ if (!definition) {
378
+ return false;
379
+ }
380
+ const matches = getUrlRegexForRoute(name).exec(url);
381
+ if (!matches) {
382
+ return false;
383
+ }
384
+ for (const k in matches.groups) {
385
+ matches.groups[k] = typeof matches.groups[k] === "string" ? decodeURIComponent(matches.groups[k]) : matches.groups[k];
386
+ }
387
+ return Object.keys(parameters).every((parameterName) => {
388
+ let value = parameters[parameterName];
389
+ const bindingProperty = definition.bindings?.[parameterName];
390
+ if (bindingProperty && typeof value === "object") {
391
+ value = value[bindingProperty];
392
+ }
393
+ return matches.groups?.[parameterName] === value.toString();
394
+ });
395
+ }
354
396
  function generateRouteFromName(name, parameters, absolute, shouldThrow) {
355
397
  const url = getUrlFromName(name, parameters, shouldThrow);
356
398
  return absolute === false ? url.toString().replace(url.origin, "") : url.toString();
357
399
  }
400
+ function getNameFromUrl(url, parameters) {
401
+ const routing = getRouting();
402
+ const routes = Object.values(routing.routes).map((x) => x.name);
403
+ return routes.find((routeName) => {
404
+ return urlMatchesRoute(url, routeName, parameters);
405
+ });
406
+ }
358
407
  function getUrlFromName(name, parameters, shouldThrow) {
359
408
  const routing = getRouting();
360
409
  const definition = getRouteDefinition(name);
@@ -367,32 +416,39 @@ function getUrlFromName(name, parameters, shouldThrow) {
367
416
  }));
368
417
  return url;
369
418
  }
370
- function getRouteTransformable(routeName, routeParameters, shouldThrow) {
419
+ function getRouteParameterValue(routeName, parameterName, routeParameters) {
371
420
  const routing = getRouting();
421
+ const definition = getRouteDefinition(routeName);
422
+ const parameters = routeParameters || {};
423
+ const value = (() => {
424
+ const value2 = parameters[parameterName];
425
+ const bindingProperty = definition.bindings?.[parameterName];
426
+ if (bindingProperty && value2 != null && typeof value2 === "object") {
427
+ return value2[bindingProperty];
428
+ }
429
+ return value2;
430
+ })();
431
+ if (value) {
432
+ const where = definition.wheres?.[parameterName];
433
+ if (where && !new RegExp(where).test(value)) {
434
+ console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
435
+ }
436
+ return value;
437
+ }
438
+ if (routing.defaults?.[parameterName]) {
439
+ return routing.defaults?.[parameterName];
440
+ }
441
+ }
442
+ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
372
443
  const definition = getRouteDefinition(routeName);
373
444
  const parameters = routeParameters || {};
374
445
  const missing = Object.keys(parameters);
375
- const replaceParameter = (match, parameterName) => {
376
- const optional = /\?}$/.test(match);
377
- const value = (() => {
378
- const value2 = parameters[parameterName];
379
- const bindingProperty = definition.bindings?.[parameterName];
380
- if (bindingProperty && typeof value2 === "object") {
381
- return value2[bindingProperty];
382
- }
383
- return value2;
384
- })();
446
+ const replaceParameter = (match, parameterName, optional) => {
447
+ const value = getRouteParameterValue(routeName, parameterName, parameters);
385
448
  missing.splice(missing.indexOf(parameterName), 1);
386
449
  if (value) {
387
- const where = definition.wheres?.[parameterName];
388
- if (where && !new RegExp(where).test(value)) {
389
- console.warn(`[hybridly:routing] Parameter [${parameterName}] does not match the required format [${where}] for route [${routeName}].`);
390
- }
391
450
  return value;
392
451
  }
393
- if (routing.defaults?.[parameterName]) {
394
- return routing.defaults?.[parameterName];
395
- }
396
452
  if (optional) {
397
453
  return "";
398
454
  }
@@ -401,8 +457,8 @@ function getRouteTransformable(routeName, routeParameters, shouldThrow) {
401
457
  }
402
458
  throw new MissingRouteParameter(parameterName, routeName);
403
459
  };
404
- const path = definition.uri.replace(/{([^}?]+)\??}/g, replaceParameter);
405
- const domain = definition.domain?.replace(/{([^}?]+)\??}/g, replaceParameter);
460
+ const path = definition.uri.replace(/{([^}?]+)(\??)}/g, replaceParameter);
461
+ const domain = definition.domain?.replace(/{([^}?]+)(\??)}/g, replaceParameter);
406
462
  const remaining = Object.keys(parameters).filter((key) => missing.includes(key)).reduce((obj, key) => ({
407
463
  ...obj,
408
464
  [key]: parameters[key]
@@ -432,30 +488,28 @@ function getRouting() {
432
488
  }
433
489
  return routing;
434
490
  }
435
-
436
- function isCurrentFromName(name, parameters, mode = "loose") {
437
- const location = window.location;
438
- const matchee = (() => {
439
- try {
440
- return makeUrl(generateRouteFromName(name, parameters, true, false));
441
- } catch (error) {
442
- }
443
- })();
444
- if (!matchee) {
445
- return false;
446
- }
447
- if (mode === "strict") {
448
- return location.href === matchee.href;
449
- }
450
- return location.href.startsWith(matchee.href);
451
- }
452
-
453
491
  function route(name, parameters, absolute) {
454
492
  return generateRouteFromName(name, parameters, absolute);
455
493
  }
456
- function current(name, parameters, mode = "loose") {
457
- return isCurrentFromName(name, parameters, mode);
494
+
495
+ function getCurrentUrl() {
496
+ if (typeof window === "undefined") {
497
+ return getInternalRouterContext().url;
498
+ }
499
+ return window.location.toString();
500
+ }
501
+ function currentRouteMatches(name, parameters) {
502
+ const namePattern = name.replaceAll(".", "\\.").replaceAll("*", ".*");
503
+ const possibleRoutes = Object.values(getRouting().routes).filter((x) => x.method.includes("GET") && RegExp(namePattern).test(x.name)).map((x) => x.name);
504
+ const currentUrl = getCurrentUrl();
505
+ return possibleRoutes.some((routeName) => {
506
+ return urlMatchesRoute(currentUrl, routeName, parameters);
507
+ });
458
508
  }
509
+ function getCurrentRouteName() {
510
+ return getNameFromUrl(getCurrentUrl());
511
+ }
512
+
459
513
  function updateRoutingConfiguration(routing) {
460
514
  if (!routing) {
461
515
  return;
@@ -646,6 +700,8 @@ const router = {
646
700
  const method = getRouteDefinition(name).method.at(0);
647
701
  return await performHybridNavigation({ url, ...options, method });
648
702
  },
703
+ matches: (name, parameters) => currentRouteMatches(name, parameters),
704
+ current: () => getCurrentRouteName(),
649
705
  dialog: {
650
706
  close: (options) => closeDialog(options)
651
707
  },
@@ -944,4 +1000,4 @@ function can(resource, action) {
944
1000
  return resource.authorization?.[action] ?? false;
945
1001
  }
946
1002
 
947
- export { can, constants, createRouter, current, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
1003
+ export { can, constants, createRouter, definePlugin, getRouterContext, makeUrl, registerHook, route, router, sameUrls };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridly/core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Core functionality of Hybridly",
5
5
  "keywords": [
6
6
  "hybridly",
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "qs": "^6.11.2",
40
- "superjson": "^1.12.3",
41
- "@hybridly/utils": "0.4.1"
40
+ "superjson": "^1.12.4",
41
+ "@hybridly/utils": "0.4.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "defu": "^6.1.2"