@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 +56 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/merge-route-matchers.d.ts +2 -0
- package/merge-route-matchers.js +6 -0
- package/package.json +1 -1
- package/router.d.ts +14 -0
- package/router.js +90 -59
- package/router.test.js +48 -1
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
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);
|
package/package.json
CHANGED
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
|
|
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(
|
|
118
|
-
replace: (params) => router.replace(
|
|
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(
|
|
132
|
-
replace: (params) => router.replace(
|
|
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
|
+
});
|