@uxf/router 11.72.4 → 11.78.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/README.md CHANGED
@@ -119,6 +119,62 @@ export default () => (
119
119
  )
120
120
  ```
121
121
 
122
+ ## RouteMatcher
123
+
124
+ ```tsx
125
+ import { createRouteMatcher } from "@app-routes";
126
+
127
+ // create active resolver
128
+ const routeMatcher = createRouteMatcher("admin/index", { param1: 123 });
129
+ // or
130
+ const routeMatcher = createRouteMatcher("admin/index");
131
+
132
+ // how to use in component
133
+
134
+ function MyComponent() {
135
+ const router = useRouter();
136
+ const isRouteActive = routeMatcher(router);
137
+
138
+ return <div>{isRouteActive ? "active" : "not active"}</div>;
139
+ }
140
+ ```
141
+
142
+ ### Custom route matchers
143
+
144
+ ```tsx
145
+ function createPathnameRouteMatcher(path: string): RouteMatcher {
146
+ return (router) => {
147
+ return router.pathname.startsWith(path);
148
+ }
149
+ }
150
+ ```
151
+
152
+ ```tsx
153
+ import { getCurrentRoute } from "@app-routes";
154
+
155
+ function createCustomRouteMatcher(): RouteMatcher {
156
+ return (router) => {
157
+ const { route, params } = getCurrentRoute(router);
158
+ if (route === "admin/index") {
159
+ // do something
160
+ } else if (route === "admin/form") {
161
+ // do something
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Merge multiple route matchers
168
+
169
+ ```tsx
170
+ import { mergeRouteMatchers } from "@uxf/router";
171
+
172
+ const routeMatcher = mergeRouteMatchers([
173
+ createRouteMatcher("admin/index"),
174
+ createRouteMatcher("admin/form"),
175
+ ]);
176
+ ```
177
+
122
178
  ## GetStaticProps
123
179
 
124
180
  ```tsx
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./helper";
2
+ export * from "./merge-route-matchers";
2
3
  export * from "./router";
3
4
  export * from "./types";
package/index.js CHANGED
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./helper"), exports);
18
+ __exportStar(require("./merge-route-matchers"), exports);
18
19
  __exportStar(require("./router"), exports);
19
20
  __exportStar(require("./types"), exports);
@@ -0,0 +1,2 @@
1
+ import { RouteMatcher } from "./router";
2
+ export declare function mergeRouteMatchers(routeMatchers: RouteMatcher[]): RouteMatcher;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeRouteMatchers = mergeRouteMatchers;
4
+ function mergeRouteMatchers(routeMatchers) {
5
+ return (router) => routeMatchers.some((routeMatcher) => routeMatcher(router));
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxf/router",
3
- "version": "11.72.4",
3
+ "version": "11.78.0",
4
4
  "description": "UXF Router",
5
5
  "author": "UXFans <dev@uxf.cz>",
6
6
  "homepage": "https://gitlab.com/uxf-npm/router#readme",
package/router.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LinkProps } from "next/link";
2
+ import { NextRouter } from "next/router";
2
3
  import { Infer, Struct } from "superstruct";
3
4
  import { SitemapGeneratorOptions, SitemapGeneratorType, SitemapRouteResolvers } from "./sitemap-generator";
4
5
  import { QueryParams, RoutesDefinition } from "./types";
@@ -14,6 +15,9 @@ type LocaleOptions<Locales extends string[]> = {
14
15
  export type FunctionParametersGenerator<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
15
16
  [K in keyof RouteList]: RouteList[K]["path"] extends string ? "schema" extends keyof RouteList[K] ? [K, ExtractSchema<RouteList[K]>, Options?] : [K, null?, Options?] : "schema" extends keyof RouteList[K] ? [K, ExtractSchema<RouteList[K]>, Options & LocaleOptions<Locales>] : [K, null, Options & LocaleOptions<Locales>];
16
17
  }[keyof RouteList];
18
+ export type FunctionParametersGeneratorWithPartialParams<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
19
+ [K in keyof RouteList]: "schema" extends keyof RouteList[K] ? [K, Partial<ExtractSchema<RouteList[K]>>?] : [K, null?];
20
+ }[keyof RouteList];
17
21
  type RouteFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => LinkProps["href"];
18
22
  type RouteToUrlFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => string;
19
23
  type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Locales extends string[], RouteList extends RoutesDefinition<Locales>> = [
@@ -23,11 +27,20 @@ type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Loca
23
27
  replace: (params: Infer<NonNullable<RouteList[T]["schema"]>>) => Promise<boolean>;
24
28
  }
25
29
  ];
30
+ type SimplyNextRouter = Pick<NextRouter, "pathname" | "query" | "isReady">;
31
+ export type RouteMatcher = (router: SimplyNextRouter) => boolean;
32
+ type CurrentRoute<RouteList extends RoutesDefinition<any>> = {
33
+ [K in keyof RouteList]: {
34
+ route: K;
35
+ params: "schema" extends keyof RouteList[K] ? ExtractSchema<RouteList[K]> | null : null;
36
+ };
37
+ }[keyof RouteList];
26
38
  type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
27
39
  route: RouteFunction<Locales, RouteList>;
28
40
  routeToUrl: RouteToUrlFunction<Locales, RouteList>;
29
41
  createSitemapGenerator: (resolvers: SitemapRouteResolvers<Locales, RouteList>, options?: SitemapGeneratorOptions) => SitemapGeneratorType;
30
42
  routes: RouteList;
43
+ getCurrentRoute: (router: SimplyNextRouter) => CurrentRoute<RouteList>;
31
44
  useActiveRoute: () => ActiveRoute<Locales, RouteList>;
32
45
  /**
33
46
  * @deprecated use useQueryParamsStatic or useQueryParams instead
@@ -35,6 +48,7 @@ type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales
35
48
  useQueryParamsDeprecated: <T extends keyof RouteList>() => QueryParams<RouteList, T>;
36
49
  useQueryParams: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<false, T, Locales, RouteList>;
37
50
  useQueryParamsStatic: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<true, T, Locales, RouteList>;
51
+ createRouteMatcher: (...args: FunctionParametersGeneratorWithPartialParams<Locales, RouteList>) => RouteMatcher;
38
52
  };
39
53
  type RouterOptions<L extends string[]> = {
40
54
  baseUrl?: string;
package/router.js CHANGED
@@ -16,6 +16,72 @@ function decodeArgs(args) {
16
16
  return { route: args[0], params: args[1], options: args[2] };
17
17
  }
18
18
  function createRouter(routes, routerOptions) {
19
+ const routeToUrl = (...args) => {
20
+ var _a;
21
+ const { route, params, options } = decodeArgs(args);
22
+ let pathname = typeof routes[route].path === "string"
23
+ ? routes[route].path
24
+ : routes[route].path[(_a = options === null || options === void 0 ? void 0 : options.locale) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)("Locale is required")];
25
+ if (!pathname) {
26
+ throw new Error(`Route '${String(route)}' not found.`);
27
+ }
28
+ const restParams = {};
29
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
30
+ Object.keys(params !== null && params !== void 0 ? params : empty_object_1.EMPTY_OBJECT).forEach((key) => {
31
+ const value = params[key];
32
+ const segment = `[${key}]`;
33
+ const catchAllSegments = `[...${key}]`;
34
+ const catchAllSegmentsOptional = `[[...${key}]]`;
35
+ if (pathname.includes(segment)) {
36
+ pathname = pathname.replace(segment, value);
37
+ }
38
+ else if (pathname.includes(catchAllSegmentsOptional)) {
39
+ if ((Array.isArray(value) && value.length === 0) ||
40
+ value === null ||
41
+ value === undefined ||
42
+ value === "") {
43
+ pathname = pathname.replace(`/${catchAllSegmentsOptional}`, "");
44
+ }
45
+ else if (Array.isArray(value)) {
46
+ // catch all segments optional
47
+ pathname = pathname.replace(catchAllSegmentsOptional, value.join("/"));
48
+ }
49
+ else {
50
+ pathname = pathname.replace(catchAllSegmentsOptional, value);
51
+ }
52
+ }
53
+ else if (pathname.includes(catchAllSegments)) {
54
+ // catch all segments
55
+ if (Array.isArray(value)) {
56
+ if (value.length === 0) {
57
+ throw new Error(`Parameter '${key}' can not be empty array for route '${String(route)}'`);
58
+ }
59
+ pathname = pathname.replace(catchAllSegments, value.join("/"));
60
+ }
61
+ else {
62
+ pathname = pathname.replace(catchAllSegments, value);
63
+ }
64
+ }
65
+ else {
66
+ restParams[key] = value;
67
+ }
68
+ });
69
+ if (Object.keys(restParams).length > 0) {
70
+ pathname = `${pathname}?${(0, qs_1.stringify)(restParams, { arrayFormat: "repeat" })}`;
71
+ }
72
+ return (options === null || options === void 0 ? void 0 : options.shouldBeAbsolute) ? `${routerOptions.baseUrl}${pathname}` : pathname;
73
+ };
74
+ const getCurrentRoute = (router) => {
75
+ const activeRoute = Object.keys(routes).find((route) => typeof routes[route].path === "string"
76
+ ? routes[route].path === router.pathname
77
+ : Object.values(routes[route].path).includes(router.pathname));
78
+ if (!activeRoute) {
79
+ throw new Error("Active route not found.");
80
+ }
81
+ const schema = routes[activeRoute].schema;
82
+ const params = router.isReady && schema ? (0, superstruct_1.mask)(router.query, schema) : null;
83
+ return { route: activeRoute, params };
84
+ };
19
85
  return {
20
86
  route(...args) {
21
87
  var _a;
@@ -28,61 +94,7 @@ function createRouter(routes, routerOptions) {
28
94
  query: params !== null && params !== void 0 ? params : null,
29
95
  };
30
96
  },
31
- routeToUrl(...args) {
32
- var _a;
33
- const { route, params, options } = decodeArgs(args);
34
- let pathname = typeof routes[route].path === "string"
35
- ? routes[route].path
36
- : routes[route].path[(_a = options === null || options === void 0 ? void 0 : options.locale) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)("Locale is required")];
37
- if (!pathname) {
38
- throw new Error(`Route '${String(route)}' not found.`);
39
- }
40
- const restParams = {};
41
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
42
- Object.keys(params !== null && params !== void 0 ? params : empty_object_1.EMPTY_OBJECT).forEach((key) => {
43
- const value = params[key];
44
- const segment = `[${key}]`;
45
- const catchAllSegments = `[...${key}]`;
46
- const catchAllSegmentsOptional = `[[...${key}]]`;
47
- if (pathname.includes(segment)) {
48
- pathname = pathname.replace(segment, value);
49
- }
50
- else if (pathname.includes(catchAllSegmentsOptional)) {
51
- if ((Array.isArray(value) && value.length === 0) ||
52
- value === null ||
53
- value === undefined ||
54
- value === "") {
55
- pathname = pathname.replace(`/${catchAllSegmentsOptional}`, "");
56
- }
57
- else if (Array.isArray(value)) {
58
- // catch all segments optional
59
- pathname = pathname.replace(catchAllSegmentsOptional, value.join("/"));
60
- }
61
- else {
62
- pathname = pathname.replace(catchAllSegmentsOptional, value);
63
- }
64
- }
65
- else if (pathname.includes(catchAllSegments)) {
66
- // catch all segments
67
- if (Array.isArray(value)) {
68
- if (value.length === 0) {
69
- throw new Error(`Parameter '${key}' can not be empty array for route '${String(route)}'`);
70
- }
71
- pathname = pathname.replace(catchAllSegments, value.join("/"));
72
- }
73
- else {
74
- pathname = pathname.replace(catchAllSegments, value);
75
- }
76
- }
77
- else {
78
- restParams[key] = value;
79
- }
80
- });
81
- if (Object.keys(restParams).length > 0) {
82
- pathname = `${pathname}?${(0, qs_1.stringify)(restParams, { arrayFormat: "repeat" })}`;
83
- }
84
- return (options === null || options === void 0 ? void 0 : options.shouldBeAbsolute) ? `${routerOptions.baseUrl}${pathname}` : pathname;
85
- },
97
+ routeToUrl,
86
98
  useActiveRoute() {
87
99
  var _a;
88
100
  const router = (0, router_1.useRouter)();
@@ -114,8 +126,8 @@ function createRouter(routes, routerOptions) {
114
126
  return [
115
127
  (0, superstruct_1.mask)(router.query, schema),
116
128
  {
117
- push: (params) => router.push(this.routeToUrl(routeName, params, {})),
118
- replace: (params) => router.replace(this.routeToUrl(routeName, params, {})),
129
+ push: (params) => router.push(routeToUrl(routeName, params, {})),
130
+ replace: (params) => router.replace(routeToUrl(routeName, params, {})),
119
131
  },
120
132
  ];
121
133
  },
@@ -128,10 +140,29 @@ function createRouter(routes, routerOptions) {
128
140
  return [
129
141
  router.isReady ? (0, superstruct_1.mask)(router.query, schema) : null,
130
142
  {
131
- push: (params) => router.push(this.routeToUrl(routeName, params, {})),
132
- replace: (params) => router.replace(this.routeToUrl(routeName, params, {})),
143
+ push: (params) => router.push(routeToUrl(routeName, params, {})),
144
+ replace: (params) => router.replace(routeToUrl(routeName, params, {})),
133
145
  },
134
146
  ];
135
147
  },
148
+ getCurrentRoute,
149
+ createRouteMatcher(...args) {
150
+ const [requiredRouteName, requiredParams] = args;
151
+ return (router) => {
152
+ const { route, params } = getCurrentRoute(router);
153
+ if (route !== requiredRouteName) {
154
+ return false;
155
+ }
156
+ if (!requiredParams || Object.keys(requiredParams).length === 0) {
157
+ return true;
158
+ }
159
+ for (const [paramName, paramValue] of Object.entries(requiredParams)) {
160
+ if ((params === null || params === void 0 ? void 0 : params[paramName]) !== paramValue) {
161
+ return false;
162
+ }
163
+ }
164
+ return true;
165
+ };
166
+ },
136
167
  };
137
168
  }
package/router.test.js CHANGED
@@ -1,15 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const superstruct_1 = require("superstruct");
4
+ const merge_route_matchers_1 = require("./merge-route-matchers");
4
5
  const router_1 = require("./router");
5
6
  const superstruct_2 = require("./superstruct");
6
- const { routeToUrl } = (0, router_1.createRouter)({
7
+ const { routeToUrl, createRouteMatcher } = (0, router_1.createRouter)({
7
8
  index: {
8
9
  path: "/",
9
10
  schema: (0, superstruct_1.object)({
10
11
  queryParam: (0, superstruct_1.optional)((0, superstruct_1.string)()),
11
12
  }),
12
13
  },
14
+ manyParameters: {
15
+ path: "/many-parameters/[param1]/form/[param2]",
16
+ schema: (0, superstruct_1.object)({
17
+ param1: (0, superstruct_1.string)(),
18
+ param2: (0, superstruct_1.string)(),
19
+ queryParam1: (0, superstruct_1.optional)((0, superstruct_1.string)()),
20
+ queryParam2: (0, superstruct_1.optional)((0, superstruct_1.string)()),
21
+ }),
22
+ },
13
23
  catchAllSegments: {
14
24
  path: "/catch-all/[...pathParams]",
15
25
  schema: (0, superstruct_1.object)({
@@ -127,3 +137,40 @@ const DATA = [
127
137
  test("routeToUrl", () => {
128
138
  DATA.map(({ actual, expected }) => expect(actual).toBe(expected));
129
139
  });
140
+ test("create route matcher", () => {
141
+ expect(createRouteMatcher("manyParameters", { param1: "value-1" })({
142
+ isReady: true,
143
+ pathname: "/many-parameters/[param1]/form/[param2]",
144
+ query: { param1: "value-1", param2: "any", queryParam1: "any", queryParam2: "any" },
145
+ })).toBe(true);
146
+ expect(createRouteMatcher("manyParameters", { param1: "value-1" })({
147
+ isReady: true,
148
+ pathname: "/many-parameters/[param1]/form/[param2]",
149
+ query: { param1: "any", param2: "any" },
150
+ })).toBe(false);
151
+ expect(createRouteMatcher("manyParameters", { param1: "value-1", queryParam1: "query-1" })({
152
+ isReady: true,
153
+ pathname: "/many-parameters/[param1]/form/[param2]",
154
+ query: { param1: "value-1", param2: "any", queryParam1: "query-1" },
155
+ })).toBe(true);
156
+ });
157
+ test("merge route matchers", () => {
158
+ const routeMatcher1 = createRouteMatcher("manyParameters", { param1: "value-1", queryParam1: "query-1" });
159
+ const routeMatcher2 = createRouteMatcher("manyParameters", { param1: "value-2", queryParam1: "query-2" });
160
+ const routeMatcher = (0, merge_route_matchers_1.mergeRouteMatchers)([routeMatcher1, routeMatcher2]);
161
+ expect(routeMatcher({
162
+ pathname: "/many-parameters/[param1]/form/[param2]",
163
+ query: { param1: "value-1", queryParam1: "query-1", param2: "any" },
164
+ isReady: true,
165
+ })).toBe(true);
166
+ expect(routeMatcher({
167
+ pathname: "/many-parameters/[param1]/form/[param2]",
168
+ query: { param1: "value-2", queryParam1: "query-2", param2: "any" },
169
+ isReady: true,
170
+ })).toBe(true);
171
+ expect(routeMatcher({
172
+ pathname: "/many-parameters/[param1]/form/[param2]",
173
+ query: { param1: "value-3", queryParam1: "query-3", param2: "any" },
174
+ isReady: true,
175
+ })).toBe(false);
176
+ });