@aminnairi/react-router 3.0.1 → 4.0.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 +31 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +353 -0
- package/package.json +12 -5
- package/index.tsx +0 -527
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ Type-safe router for the React library
|
|
|
26
26
|
- [Transition](#transition)
|
|
27
27
|
- [Error handling](#error-handling)
|
|
28
28
|
- [License](#license)
|
|
29
|
+
- [Contributing](../CONTRIBUTING.md)
|
|
29
30
|
- [Changelogs](#changelogs)
|
|
30
31
|
- [Versions](#versions)
|
|
31
32
|
- [3.0.0](#300)
|
|
@@ -935,6 +936,8 @@ See [`LICENSE`](./LICENSE).
|
|
|
935
936
|
|
|
936
937
|
### Versions
|
|
937
938
|
|
|
939
|
+
- [`4.0.0`](#400)
|
|
940
|
+
- [`3.0.2`](#302)
|
|
938
941
|
- [`3.0.1`](#301)
|
|
939
942
|
- [`3.0.0`](#300)
|
|
940
943
|
- [`2.1.0`](#210)
|
|
@@ -946,6 +949,34 @@ See [`LICENSE`](./LICENSE).
|
|
|
946
949
|
- [`0.1.1`](#011)
|
|
947
950
|
- [`0.1.0`](#010)
|
|
948
951
|
|
|
952
|
+
### 4.0.0
|
|
953
|
+
|
|
954
|
+
#### Major changes
|
|
955
|
+
|
|
956
|
+
- Externalized React from the build output to prevent hooks mismatch issues with consumer applications. Consumers are now required to have React as a dependency in their project.
|
|
957
|
+
|
|
958
|
+
#### Minor changes
|
|
959
|
+
|
|
960
|
+
- Explicitly set the build output format to ESM.
|
|
961
|
+
|
|
962
|
+
#### Bug & security fixes
|
|
963
|
+
|
|
964
|
+
- Fixed lint error in the source code dependencies.
|
|
965
|
+
|
|
966
|
+
### 3.0.2
|
|
967
|
+
|
|
968
|
+
#### Major changes
|
|
969
|
+
|
|
970
|
+
None.
|
|
971
|
+
|
|
972
|
+
#### Minor changes
|
|
973
|
+
|
|
974
|
+
None.
|
|
975
|
+
|
|
976
|
+
#### Bug & security fixes
|
|
977
|
+
|
|
978
|
+
- Fixed the output file path in the rolldown configuration (changed from `dist/index.ts` to `dist/index.js`)
|
|
979
|
+
|
|
949
980
|
### 3.0.1
|
|
950
981
|
|
|
951
982
|
#### Major changes
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type Dispatch, type FunctionComponent, type ReactNode, type SetStateAction } from "react";
|
|
2
|
+
export interface IssueProps {
|
|
3
|
+
error: Error | null;
|
|
4
|
+
resetError: () => void;
|
|
5
|
+
}
|
|
6
|
+
export interface Router<Path extends string, Locale> {
|
|
7
|
+
prefix?: string;
|
|
8
|
+
locales?: Locale[];
|
|
9
|
+
transition?: Transition;
|
|
10
|
+
fallback: FunctionComponent;
|
|
11
|
+
issue: FunctionComponent<IssueProps>;
|
|
12
|
+
pages: Array<Page<Path>>;
|
|
13
|
+
}
|
|
14
|
+
export interface RouterProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export interface RouterContextInterface<Locale> {
|
|
18
|
+
locale: Locale | null;
|
|
19
|
+
prefix: string | null;
|
|
20
|
+
path: string;
|
|
21
|
+
setLocale: Dispatch<SetStateAction<Locale | null>>;
|
|
22
|
+
}
|
|
23
|
+
export type NavigationDirection = "forward" | "backward";
|
|
24
|
+
export declare class Uri<Locale> {
|
|
25
|
+
readonly path: string;
|
|
26
|
+
readonly prefix: string | null;
|
|
27
|
+
readonly locale: Locale | null;
|
|
28
|
+
private constructor();
|
|
29
|
+
static from<Locale>(uri: string, expectedPrefix?: string, expectedLocales?: Locale[]): Uri<Locale>;
|
|
30
|
+
}
|
|
31
|
+
export type ExtractParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractParams<Rest> : Path extends `${string}:${infer Param}` ? Param : never;
|
|
32
|
+
export type Params<Path extends string> = {
|
|
33
|
+
[Key in ExtractParams<Path>]: string;
|
|
34
|
+
};
|
|
35
|
+
export interface PageParams<Path extends string> {
|
|
36
|
+
parameters: Params<Path>;
|
|
37
|
+
}
|
|
38
|
+
export interface Page<Path extends string> {
|
|
39
|
+
path: Path;
|
|
40
|
+
element: FunctionComponent<PageParams<Path>>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createPage<Path extends string>(page: Page<Path>): Page<Path>;
|
|
43
|
+
export type Transition = (direction: "forward" | "backward", next: () => void) => void;
|
|
44
|
+
export declare const scaleFadeTransition: Transition;
|
|
45
|
+
export declare const crossFadeTransition: Transition;
|
|
46
|
+
export declare const slideHorizontalTransition: Transition;
|
|
47
|
+
export declare const slideVerticalTransition: Transition;
|
|
48
|
+
export declare function createRouter<Locale extends string = never, Path extends string = never>({ prefix: expectedPrefix, locales, pages, fallback: Fallback, issue: Issue, transition }: Router<Path, Locale>): {
|
|
49
|
+
RouterProvider: ({ children }: RouterProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
RouterView: () => import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
useLocale: () => {
|
|
52
|
+
locale: Locale | null;
|
|
53
|
+
setLocale: (locale: Locale) => void;
|
|
54
|
+
};
|
|
55
|
+
usePrefix: () => {
|
|
56
|
+
prefix: string | null;
|
|
57
|
+
};
|
|
58
|
+
usePath: () => {
|
|
59
|
+
path: string;
|
|
60
|
+
};
|
|
61
|
+
useNavigateToPage: <P extends Path>(page: Page<P>) => (...[params]: ExtractParams<P> extends never ? [] : [Params<P>]) => void;
|
|
62
|
+
useIsActivePage: <P extends Path>(page: Page<P>) => boolean;
|
|
63
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { Component, createContext, useCallback, useContext, useEffect, useEffectEvent, useMemo, useState } from "react";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
|
|
6
|
+
/**
|
|
7
|
+
* @license React
|
|
8
|
+
* react-jsx-runtime.production.js
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
11
|
+
*
|
|
12
|
+
* This source code is licensed under the MIT license found in the
|
|
13
|
+
* LICENSE file in the root directory of this source tree.
|
|
14
|
+
*/
|
|
15
|
+
var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
16
|
+
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
|
|
17
|
+
function jsxProd(type, config, maybeKey) {
|
|
18
|
+
var key = null;
|
|
19
|
+
void 0 !== maybeKey && (key = "" + maybeKey);
|
|
20
|
+
void 0 !== config.key && (key = "" + config.key);
|
|
21
|
+
if ("key" in config) {
|
|
22
|
+
maybeKey = {};
|
|
23
|
+
for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]);
|
|
24
|
+
} else maybeKey = config;
|
|
25
|
+
config = maybeKey.ref;
|
|
26
|
+
return {
|
|
27
|
+
$$typeof: REACT_ELEMENT_TYPE,
|
|
28
|
+
type,
|
|
29
|
+
key,
|
|
30
|
+
ref: void 0 !== config ? config : null,
|
|
31
|
+
props: maybeKey
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
exports.jsx = jsxProd;
|
|
35
|
+
}));
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region index.tsx
|
|
38
|
+
var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
39
|
+
module.exports = require_react_jsx_runtime_production();
|
|
40
|
+
})))();
|
|
41
|
+
function normalize(uri) {
|
|
42
|
+
return uri.trim().toLowerCase().replace(/\/+/g, "/").replace(/^\/+|\/$/g, "");
|
|
43
|
+
}
|
|
44
|
+
function matchPath(path, pathname) {
|
|
45
|
+
const pathParts = normalize(path).split("/").filter(Boolean);
|
|
46
|
+
const pathnameParts = normalize(pathname).split("/").filter(Boolean);
|
|
47
|
+
return pathParts.length === pathnameParts.length && pathParts.every((pathPart, index) => {
|
|
48
|
+
return pathPart.startsWith(":") || pathPart === pathnameParts.at(index);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function matchParameters(path, pathname) {
|
|
52
|
+
const pathParts = normalize(path).split("/").filter(Boolean);
|
|
53
|
+
const pathnameParts = normalize(pathname).split("/").filter(Boolean);
|
|
54
|
+
if (pathParts.length !== pathnameParts.length) return {};
|
|
55
|
+
return pathParts.reduce((parameters, pathPart, index) => {
|
|
56
|
+
if (!pathPart.startsWith(":")) return parameters;
|
|
57
|
+
return {
|
|
58
|
+
...parameters,
|
|
59
|
+
[pathPart.slice(1)]: pathnameParts.at(index) ?? ""
|
|
60
|
+
};
|
|
61
|
+
}, {});
|
|
62
|
+
}
|
|
63
|
+
var Uri = class Uri {
|
|
64
|
+
constructor(path, prefix, locale) {
|
|
65
|
+
this.path = path;
|
|
66
|
+
this.prefix = prefix;
|
|
67
|
+
this.locale = locale;
|
|
68
|
+
}
|
|
69
|
+
static from(uri, expectedPrefix, expectedLocales) {
|
|
70
|
+
const [prefixOrLocale, localeOrNothing, ...parts] = normalize(uri).split("/");
|
|
71
|
+
const locales = expectedLocales ?? [];
|
|
72
|
+
if (expectedPrefix && prefixOrLocale && prefixOrLocale === normalize(expectedPrefix)) {
|
|
73
|
+
const locale = locales.find((expectedLocale) => expectedLocale === localeOrNothing);
|
|
74
|
+
if (locale) return new Uri(parts.join("/"), prefixOrLocale, locale);
|
|
75
|
+
return new Uri([localeOrNothing, ...parts].join("/"), prefixOrLocale, null);
|
|
76
|
+
}
|
|
77
|
+
const locale = locales.find((expectedLocale) => expectedLocale === prefixOrLocale);
|
|
78
|
+
if (locale) return new Uri([localeOrNothing, ...parts].join("/"), null, locale);
|
|
79
|
+
return new Uri([
|
|
80
|
+
prefixOrLocale,
|
|
81
|
+
localeOrNothing,
|
|
82
|
+
...parts
|
|
83
|
+
].join("/"), null, null);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var ErrorBoundary = class extends Component {
|
|
87
|
+
constructor(props) {
|
|
88
|
+
super(props);
|
|
89
|
+
this.state = {
|
|
90
|
+
error: null,
|
|
91
|
+
resetError: this.resetError.bind(this)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
resetError() {
|
|
95
|
+
this.setState({ error: null });
|
|
96
|
+
}
|
|
97
|
+
static getDerivedStateFromError(error) {
|
|
98
|
+
return { error };
|
|
99
|
+
}
|
|
100
|
+
componentDidCatch(error) {
|
|
101
|
+
this.setState({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
102
|
+
}
|
|
103
|
+
render() {
|
|
104
|
+
if (this.state.error) {
|
|
105
|
+
const Issue = this.props.issue;
|
|
106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Issue, {
|
|
107
|
+
error: this.state.error,
|
|
108
|
+
resetError: this.state.resetError
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return this.props.children;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function createPage(page) {
|
|
115
|
+
return {
|
|
116
|
+
...page,
|
|
117
|
+
path: normalize(page.path)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const scaleFadeTransition = async (direction, next) => {
|
|
121
|
+
try {
|
|
122
|
+
await document.startViewTransition(() => {
|
|
123
|
+
next();
|
|
124
|
+
}).ready;
|
|
125
|
+
document.documentElement.animate([{
|
|
126
|
+
transform: "scale(1)",
|
|
127
|
+
opacity: 1
|
|
128
|
+
}, {
|
|
129
|
+
transform: direction === "forward" ? "scale(1.04)" : "scale(0.96)",
|
|
130
|
+
opacity: 0
|
|
131
|
+
}], {
|
|
132
|
+
duration: 200,
|
|
133
|
+
easing: "ease-in-out",
|
|
134
|
+
fill: "both",
|
|
135
|
+
pseudoElement: `::view-transition-old(root)`
|
|
136
|
+
});
|
|
137
|
+
document.documentElement.animate([{
|
|
138
|
+
transform: direction === "forward" ? "scale(0.96)" : "scale(1.04)",
|
|
139
|
+
opacity: 0
|
|
140
|
+
}, {
|
|
141
|
+
transform: "scale(1)",
|
|
142
|
+
opacity: 1
|
|
143
|
+
}], {
|
|
144
|
+
duration: 200,
|
|
145
|
+
easing: "ease-in-out",
|
|
146
|
+
fill: "both",
|
|
147
|
+
pseudoElement: `::view-transition-new(root)`
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(error);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const crossFadeTransition = async (direction, next) => {
|
|
154
|
+
try {
|
|
155
|
+
await document.startViewTransition(() => {
|
|
156
|
+
next();
|
|
157
|
+
}).ready;
|
|
158
|
+
document.documentElement.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
159
|
+
duration: 200,
|
|
160
|
+
easing: "ease-in-out",
|
|
161
|
+
fill: "both",
|
|
162
|
+
pseudoElement: `::view-transition-old(root)`
|
|
163
|
+
});
|
|
164
|
+
document.documentElement.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
165
|
+
duration: 200,
|
|
166
|
+
easing: "ease-in-out",
|
|
167
|
+
fill: "both",
|
|
168
|
+
pseudoElement: `::view-transition-new(root)`
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(error);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const slideHorizontalTransition = async (direction, next) => {
|
|
175
|
+
try {
|
|
176
|
+
await document.startViewTransition(() => {
|
|
177
|
+
next();
|
|
178
|
+
}).ready;
|
|
179
|
+
document.documentElement.animate([{ transform: "translateX(0)" }, { transform: direction === "forward" ? "translateX(-100%)" : "translateX(100%)" }], {
|
|
180
|
+
duration: 200,
|
|
181
|
+
easing: "ease-in-out",
|
|
182
|
+
fill: "both",
|
|
183
|
+
pseudoElement: `::view-transition-old(root)`
|
|
184
|
+
});
|
|
185
|
+
document.documentElement.animate([{ transform: direction === "forward" ? "translateX(100%)" : "translateX(-100%)" }, { transform: "translateX(0)" }], {
|
|
186
|
+
duration: 200,
|
|
187
|
+
easing: "ease-in-out",
|
|
188
|
+
fill: "both",
|
|
189
|
+
pseudoElement: `::view-transition-new(root)`
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error(error);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const slideVerticalTransition = async (direction, next) => {
|
|
196
|
+
try {
|
|
197
|
+
await document.startViewTransition(() => {
|
|
198
|
+
next();
|
|
199
|
+
}).ready;
|
|
200
|
+
document.documentElement.animate([{ transform: "translateY(0)" }, { transform: direction === "forward" ? "translateY(-100%)" : "translateY(100%)" }], {
|
|
201
|
+
duration: 200,
|
|
202
|
+
easing: "ease-in-out",
|
|
203
|
+
fill: "both",
|
|
204
|
+
pseudoElement: `::view-transition-old(root)`
|
|
205
|
+
});
|
|
206
|
+
document.documentElement.animate([{ transform: direction === "forward" ? "translateY(100%)" : "translateY(-100%)" }, { transform: "translateY(0)" }], {
|
|
207
|
+
duration: 200,
|
|
208
|
+
easing: "ease-in-out",
|
|
209
|
+
fill: "both",
|
|
210
|
+
pseudoElement: `::view-transition-new(root)`
|
|
211
|
+
});
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(error);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
function createRouter({ prefix: expectedPrefix, locales, pages, fallback: Fallback, issue: Issue, transition }) {
|
|
217
|
+
const RouterContext = createContext({
|
|
218
|
+
locale: null,
|
|
219
|
+
prefix: null,
|
|
220
|
+
path: "/",
|
|
221
|
+
setLocale: () => {}
|
|
222
|
+
});
|
|
223
|
+
function useIsActivePage(page) {
|
|
224
|
+
const { path } = usePath();
|
|
225
|
+
return matchPath(page.path, path);
|
|
226
|
+
}
|
|
227
|
+
function useLocale() {
|
|
228
|
+
const context = useContext(RouterContext);
|
|
229
|
+
if (!context) throw new Error("component using the useLocale hook has not been wrapped inside RouterProvider.");
|
|
230
|
+
return {
|
|
231
|
+
locale: context.locale,
|
|
232
|
+
setLocale: (locale) => {
|
|
233
|
+
if (!locales?.includes(locale)) return;
|
|
234
|
+
window.history.pushState(null, "", `/${normalize(`${context.prefix ?? ""}/${locale}/${context.path}`)}`);
|
|
235
|
+
window.dispatchEvent(new Event("pushstate"));
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function usePrefix() {
|
|
240
|
+
const context = useContext(RouterContext);
|
|
241
|
+
if (!context) throw new Error("Component using the usePrefix hook has not been wrapped inside RouterProvider");
|
|
242
|
+
return { prefix: context.prefix };
|
|
243
|
+
}
|
|
244
|
+
function usePath() {
|
|
245
|
+
const context = useContext(RouterContext);
|
|
246
|
+
if (!context) throw new Error("Component using the usePath hook has not been wrapped inside RouterProvider");
|
|
247
|
+
return { path: context.path };
|
|
248
|
+
}
|
|
249
|
+
function useNavigateToPage(page) {
|
|
250
|
+
const { locale } = useLocale();
|
|
251
|
+
const { prefix } = usePrefix();
|
|
252
|
+
return useCallback((...[params]) => {
|
|
253
|
+
const path = Object.entries(params ?? {}).reduce((oldParams, [name, value]) => {
|
|
254
|
+
return oldParams.replace(`:${name}`, String(value));
|
|
255
|
+
}, page.path);
|
|
256
|
+
const pathname = `/${normalize(`${prefix ?? ""}/${locale ?? ""}/${path}`)}`;
|
|
257
|
+
console.log({ pathname });
|
|
258
|
+
window.history.pushState(null, "", pathname);
|
|
259
|
+
window.dispatchEvent(new Event("pushstate"));
|
|
260
|
+
}, [
|
|
261
|
+
page,
|
|
262
|
+
locale,
|
|
263
|
+
prefix
|
|
264
|
+
]);
|
|
265
|
+
}
|
|
266
|
+
function RouterProvider({ children }) {
|
|
267
|
+
const uri = useMemo(() => Uri.from(window.location.pathname, expectedPrefix, locales), []);
|
|
268
|
+
const [locale, setLocale] = useState(uri.locale);
|
|
269
|
+
const [path, setPath] = useState(uri.path);
|
|
270
|
+
const [prefix, setPrefix] = useState(uri.prefix);
|
|
271
|
+
function setHash(newHash) {
|
|
272
|
+
window.location.hash = newHash;
|
|
273
|
+
}
|
|
274
|
+
const value = useMemo(() => {
|
|
275
|
+
return {
|
|
276
|
+
locale,
|
|
277
|
+
path,
|
|
278
|
+
prefix: prefix ?? null,
|
|
279
|
+
setLocale,
|
|
280
|
+
setHash
|
|
281
|
+
};
|
|
282
|
+
}, [
|
|
283
|
+
locale,
|
|
284
|
+
prefix,
|
|
285
|
+
path
|
|
286
|
+
]);
|
|
287
|
+
const onNavigation = useEffectEvent((direction) => {
|
|
288
|
+
const pathname = normalize(window.location.pathname);
|
|
289
|
+
const uri = Uri.from(pathname, expectedPrefix, locales);
|
|
290
|
+
const defaultTransition = (_, next) => {
|
|
291
|
+
next();
|
|
292
|
+
};
|
|
293
|
+
(transition ?? defaultTransition)(direction, () => {
|
|
294
|
+
setLocale(uri.locale);
|
|
295
|
+
setPath(uri.path);
|
|
296
|
+
setPrefix(uri.prefix);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
const onMount = useEffectEvent(() => {
|
|
300
|
+
const pathname = `/${normalize(window.location.pathname)}`;
|
|
301
|
+
const uri = Uri.from(pathname, expectedPrefix, [locale, ...locales ?? []]);
|
|
302
|
+
const newPathname = `/${normalize(`${uri.prefix ?? expectedPrefix ?? ""}/${uri.locale ?? locales?.at(0) ?? ""}/${uri.path}`)}`;
|
|
303
|
+
if (newPathname !== pathname) {
|
|
304
|
+
window.history.pushState(null, "", newPathname);
|
|
305
|
+
window.dispatchEvent(new Event("pushstate"));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
const abortController = new AbortController();
|
|
310
|
+
window.addEventListener("pushstate", () => {
|
|
311
|
+
onNavigation("forward");
|
|
312
|
+
}, abortController);
|
|
313
|
+
window.addEventListener("popstate", () => {
|
|
314
|
+
onNavigation("backward");
|
|
315
|
+
}, abortController);
|
|
316
|
+
onMount();
|
|
317
|
+
return () => {
|
|
318
|
+
abortController.abort();
|
|
319
|
+
};
|
|
320
|
+
}, [onMount, onNavigation]);
|
|
321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouterContext.Provider, {
|
|
322
|
+
value,
|
|
323
|
+
children
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function RouterView() {
|
|
327
|
+
const { path } = usePath();
|
|
328
|
+
const foundPage = useMemo(() => {
|
|
329
|
+
return pages.find((page) => {
|
|
330
|
+
return matchPath(page.path, path);
|
|
331
|
+
});
|
|
332
|
+
}, [path]);
|
|
333
|
+
if (foundPage) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, {
|
|
334
|
+
issue: Issue,
|
|
335
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(foundPage.element, { parameters: matchParameters(foundPage.path, path) })
|
|
336
|
+
});
|
|
337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, {
|
|
338
|
+
issue: Issue,
|
|
339
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Fallback, {})
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
RouterProvider,
|
|
344
|
+
RouterView,
|
|
345
|
+
useLocale,
|
|
346
|
+
usePrefix,
|
|
347
|
+
usePath,
|
|
348
|
+
useNavigateToPage,
|
|
349
|
+
useIsActivePage
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
export { Uri, createPage, createRouter, crossFadeTransition, scaleFadeTransition, slideHorizontalTransition, slideVerticalTransition };
|
package/package.json
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@aminnairi/react-router",
|
|
4
4
|
"description": "Type-safe router for the React library",
|
|
5
|
-
"version": "
|
|
6
|
-
"homepage": "https://github.com/aminnairi/react-router
|
|
5
|
+
"version": "4.0.0",
|
|
6
|
+
"homepage": "https://github.com/aminnairi/react-router",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"readme": "https://github.com/aminnairi/react-router#readme",
|
|
8
11
|
"files": [
|
|
9
|
-
"index.
|
|
12
|
+
"dist/index.js",
|
|
13
|
+
"dist/index.d.ts"
|
|
10
14
|
],
|
|
11
15
|
"bugs": {
|
|
12
16
|
"url": "https://github.com/aminnairi/react-router/issues"
|
|
@@ -28,16 +32,19 @@
|
|
|
28
32
|
"history"
|
|
29
33
|
],
|
|
30
34
|
"peerDependencies": {
|
|
31
|
-
"react": "
|
|
35
|
+
"react": "^19.2.4"
|
|
32
36
|
},
|
|
33
37
|
"scripts": {
|
|
34
|
-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
|
38
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
39
|
+
"build": "tsc && rolldown --config rolldown.config.ts"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
42
|
+
"@types/react": "^19.2.14",
|
|
37
43
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
38
44
|
"@typescript-eslint/parser": "^7.2.0",
|
|
39
45
|
"eslint": "^8.57.0",
|
|
40
46
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
47
|
+
"rolldown": "^1.0.0-rc.9",
|
|
41
48
|
"typescript": "^5.9.3"
|
|
42
49
|
}
|
|
43
50
|
}
|
package/index.tsx
DELETED
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
import { Component, createContext, type Dispatch, type FunctionComponent, type ReactNode, type SetStateAction, useCallback, useContext, useEffect, useEffectEvent, useMemo, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export interface IssueProps {
|
|
4
|
-
error: Error | null,
|
|
5
|
-
resetError: () => void
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface Router<Path extends string, Locale> {
|
|
9
|
-
prefix?: string
|
|
10
|
-
locales?: Locale[]
|
|
11
|
-
transition?: Transition
|
|
12
|
-
fallback: FunctionComponent
|
|
13
|
-
issue: FunctionComponent<IssueProps>
|
|
14
|
-
pages: Array<Page<Path>>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface RouterProviderProps {
|
|
18
|
-
children: ReactNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface RouterContextInterface<Locale> {
|
|
22
|
-
locale: Locale | null
|
|
23
|
-
prefix: string | null
|
|
24
|
-
path: string
|
|
25
|
-
setLocale: Dispatch<SetStateAction<Locale | null>>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type NavigationDirection = "forward" | "backward";
|
|
29
|
-
|
|
30
|
-
function normalize(uri: string) {
|
|
31
|
-
return uri
|
|
32
|
-
.trim()
|
|
33
|
-
.toLowerCase()
|
|
34
|
-
.replace(/\/+/g, "/")
|
|
35
|
-
.replace(/^\/+|\/$/g, "");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function matchPath(path: string, pathname: string) {
|
|
39
|
-
const pathParts = normalize(path).split("/").filter(Boolean);
|
|
40
|
-
const pathnameParts = normalize(pathname).split("/").filter(Boolean);
|
|
41
|
-
|
|
42
|
-
return pathParts.length === pathnameParts.length && pathParts.every((pathPart, index) => {
|
|
43
|
-
return pathPart.startsWith(":") || pathPart === pathnameParts.at(index);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function matchParameters(path: string, pathname: string): Record<string, string> {
|
|
48
|
-
const pathParts = normalize(path).split("/").filter(Boolean);
|
|
49
|
-
const pathnameParts = normalize(pathname).split("/").filter(Boolean);
|
|
50
|
-
|
|
51
|
-
if (pathParts.length !== pathnameParts.length) {
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return pathParts.reduce((parameters, pathPart, index) => {
|
|
56
|
-
if (!pathPart.startsWith(":")) {
|
|
57
|
-
return parameters;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
...parameters,
|
|
62
|
-
[pathPart.slice(1)]: pathnameParts.at(index) ?? ""
|
|
63
|
-
}
|
|
64
|
-
}, {});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export class Uri<Locale> {
|
|
68
|
-
private constructor(public readonly path: string, public readonly prefix: string | null, public readonly locale: Locale | null) { }
|
|
69
|
-
|
|
70
|
-
public static from<Locale>(uri: string, expectedPrefix?: string, expectedLocales?: Locale[]): Uri<Locale> {
|
|
71
|
-
const [prefixOrLocale, localeOrNothing, ...parts] = normalize(uri).split("/");
|
|
72
|
-
const locales = expectedLocales ?? [];
|
|
73
|
-
|
|
74
|
-
if (expectedPrefix && prefixOrLocale && prefixOrLocale === normalize(expectedPrefix)) {
|
|
75
|
-
const locale = locales.find(expectedLocale => expectedLocale === localeOrNothing);
|
|
76
|
-
|
|
77
|
-
if (locale) {
|
|
78
|
-
return new Uri<Locale>(
|
|
79
|
-
parts.join("/"),
|
|
80
|
-
prefixOrLocale,
|
|
81
|
-
locale,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return new Uri<Locale>(
|
|
86
|
-
[localeOrNothing, ...parts].join("/"),
|
|
87
|
-
prefixOrLocale,
|
|
88
|
-
null
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const locale = locales.find(expectedLocale => expectedLocale === prefixOrLocale);
|
|
93
|
-
|
|
94
|
-
if (locale) {
|
|
95
|
-
return new Uri<Locale>(
|
|
96
|
-
[localeOrNothing, ...parts].join("/"),
|
|
97
|
-
null,
|
|
98
|
-
locale
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return new Uri<Locale>(
|
|
103
|
-
[prefixOrLocale, localeOrNothing, ...parts].join("/"),
|
|
104
|
-
null,
|
|
105
|
-
null
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export type ExtractParams<Path extends string> =
|
|
111
|
-
Path extends `${string}:${infer Param}/${infer Rest}`
|
|
112
|
-
? Param | ExtractParams<Rest>
|
|
113
|
-
: Path extends `${string}:${infer Param}`
|
|
114
|
-
? Param
|
|
115
|
-
: never
|
|
116
|
-
|
|
117
|
-
export type Params<Path extends string> = {
|
|
118
|
-
[Key in ExtractParams<Path>]: string
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface PageParams<Path extends string> {
|
|
122
|
-
parameters: Params<Path>
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface Page<Path extends string> {
|
|
126
|
-
path: Path
|
|
127
|
-
element: FunctionComponent<PageParams<Path>>
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
interface ErrorBoundaryProps {
|
|
131
|
-
children: ReactNode;
|
|
132
|
-
issue: FunctionComponent<IssueProps>;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
class ErrorBoundary extends Component<ErrorBoundaryProps, IssueProps> {
|
|
136
|
-
public constructor(props: ErrorBoundaryProps) {
|
|
137
|
-
super(props);
|
|
138
|
-
|
|
139
|
-
this.state = {
|
|
140
|
-
error: null,
|
|
141
|
-
resetError: this.resetError.bind(this)
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private resetError() {
|
|
146
|
-
this.setState({ error: null });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public static getDerivedStateFromError(error: Error) {
|
|
150
|
-
return {
|
|
151
|
-
error
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public override componentDidCatch(error: unknown) {
|
|
156
|
-
this.setState({
|
|
157
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
public override render() {
|
|
162
|
-
if (this.state.error) {
|
|
163
|
-
const Issue = this.props.issue;
|
|
164
|
-
|
|
165
|
-
return <Issue error={this.state.error} resetError={this.state.resetError} />;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return this.props.children;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function createPage<Path extends string>(page: Page<Path>): Page<Path> {
|
|
173
|
-
return {
|
|
174
|
-
...page,
|
|
175
|
-
path: normalize(page.path) as Path
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export type Transition = (direction: "forward" | "backward", next: () => void) => void;
|
|
180
|
-
|
|
181
|
-
export const scaleFadeTransition: Transition = async (direction: NavigationDirection, next) => {
|
|
182
|
-
try {
|
|
183
|
-
const transition = document.startViewTransition(() => {
|
|
184
|
-
next();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
await transition.ready;
|
|
188
|
-
|
|
189
|
-
document.documentElement.animate(
|
|
190
|
-
[
|
|
191
|
-
{
|
|
192
|
-
transform: 'scale(1)',
|
|
193
|
-
opacity: 1
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
transform: direction === "forward" ? "scale(1.04)" : "scale(0.96)",
|
|
197
|
-
opacity: 0
|
|
198
|
-
}
|
|
199
|
-
],
|
|
200
|
-
{
|
|
201
|
-
duration: 200,
|
|
202
|
-
easing: "ease-in-out",
|
|
203
|
-
fill: "both",
|
|
204
|
-
pseudoElement: `::view-transition-old(root)`,
|
|
205
|
-
}
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
document.documentElement.animate(
|
|
209
|
-
[
|
|
210
|
-
{
|
|
211
|
-
transform: direction === "forward" ? "scale(0.96)" : "scale(1.04)",
|
|
212
|
-
opacity: 0
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
transform: 'scale(1)',
|
|
216
|
-
opacity: 1
|
|
217
|
-
}
|
|
218
|
-
],
|
|
219
|
-
{
|
|
220
|
-
duration: 200,
|
|
221
|
-
easing: "ease-in-out",
|
|
222
|
-
fill: "both",
|
|
223
|
-
pseudoElement: `::view-transition-new(root)`,
|
|
224
|
-
}
|
|
225
|
-
);
|
|
226
|
-
} catch (error) {
|
|
227
|
-
console.error(error);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export const crossFadeTransition: Transition = async (direction: NavigationDirection, next) => {
|
|
232
|
-
try {
|
|
233
|
-
const transition = document.startViewTransition(() => {
|
|
234
|
-
next();
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await transition.ready;
|
|
238
|
-
|
|
239
|
-
document.documentElement.animate(
|
|
240
|
-
[
|
|
241
|
-
{ opacity: 1 },
|
|
242
|
-
{ opacity: 0 }
|
|
243
|
-
],
|
|
244
|
-
{
|
|
245
|
-
duration: 200,
|
|
246
|
-
easing: "ease-in-out",
|
|
247
|
-
fill: "both",
|
|
248
|
-
pseudoElement: `::view-transition-old(root)`,
|
|
249
|
-
}
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
document.documentElement.animate(
|
|
253
|
-
[
|
|
254
|
-
{ opacity: 0 },
|
|
255
|
-
{ opacity: 1 }
|
|
256
|
-
],
|
|
257
|
-
{
|
|
258
|
-
duration: 200,
|
|
259
|
-
easing: "ease-in-out",
|
|
260
|
-
fill: "both",
|
|
261
|
-
pseudoElement: `::view-transition-new(root)`,
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.error(error);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export const slideHorizontalTransition: Transition = async (direction: NavigationDirection, next) => {
|
|
270
|
-
try {
|
|
271
|
-
const transition = document.startViewTransition(() => {
|
|
272
|
-
next();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await transition.ready;
|
|
276
|
-
|
|
277
|
-
document.documentElement.animate(
|
|
278
|
-
[
|
|
279
|
-
{ transform: 'translateX(0)' },
|
|
280
|
-
{ transform: direction === "forward" ? 'translateX(-100%)' : 'translateX(100%)' }
|
|
281
|
-
],
|
|
282
|
-
{
|
|
283
|
-
duration: 200,
|
|
284
|
-
easing: "ease-in-out",
|
|
285
|
-
fill: "both",
|
|
286
|
-
pseudoElement: `::view-transition-old(root)`,
|
|
287
|
-
}
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
document.documentElement.animate(
|
|
291
|
-
[
|
|
292
|
-
{ transform: direction === "forward" ? 'translateX(100%)' : 'translateX(-100%)' },
|
|
293
|
-
{ transform: 'translateX(0)' }
|
|
294
|
-
],
|
|
295
|
-
{
|
|
296
|
-
duration: 200,
|
|
297
|
-
easing: "ease-in-out",
|
|
298
|
-
fill: "both",
|
|
299
|
-
pseudoElement: `::view-transition-new(root)`,
|
|
300
|
-
}
|
|
301
|
-
);
|
|
302
|
-
} catch (error) {
|
|
303
|
-
console.error(error);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export const slideVerticalTransition: Transition = async (direction: NavigationDirection, next) => {
|
|
308
|
-
try {
|
|
309
|
-
const transition = document.startViewTransition(() => {
|
|
310
|
-
next();
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
await transition.ready;
|
|
314
|
-
|
|
315
|
-
document.documentElement.animate(
|
|
316
|
-
[
|
|
317
|
-
{ transform: 'translateY(0)' },
|
|
318
|
-
{ transform: direction === "forward" ? 'translateY(-100%)' : 'translateY(100%)' }
|
|
319
|
-
],
|
|
320
|
-
{
|
|
321
|
-
duration: 200,
|
|
322
|
-
easing: "ease-in-out",
|
|
323
|
-
fill: "both",
|
|
324
|
-
pseudoElement: `::view-transition-old(root)`,
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
document.documentElement.animate(
|
|
329
|
-
[
|
|
330
|
-
{ transform: direction === "forward" ? 'translateY(100%)' : 'translateY(-100%)' },
|
|
331
|
-
{ transform: 'translateY(0)' }
|
|
332
|
-
],
|
|
333
|
-
{
|
|
334
|
-
duration: 200,
|
|
335
|
-
easing: "ease-in-out",
|
|
336
|
-
fill: "both",
|
|
337
|
-
pseudoElement: `::view-transition-new(root)`,
|
|
338
|
-
}
|
|
339
|
-
);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error(error);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function createRouter<Locale extends string = never, Path extends string = never>({ prefix: expectedPrefix, locales, pages, fallback: Fallback, issue: Issue, transition }: Router<Path, Locale>) {
|
|
346
|
-
const RouterContext = createContext<RouterContextInterface<Locale>>({
|
|
347
|
-
locale: null,
|
|
348
|
-
prefix: null,
|
|
349
|
-
path: "/",
|
|
350
|
-
setLocale: () => { },
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
function useIsActivePage<P extends Path>(page: Page<P>): boolean {
|
|
355
|
-
const { path } = usePath();
|
|
356
|
-
return matchPath(page.path, path);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function useLocale() {
|
|
360
|
-
const context = useContext(RouterContext);
|
|
361
|
-
|
|
362
|
-
if (!context) {
|
|
363
|
-
throw new Error("component using the useLocale hook has not been wrapped inside RouterProvider.");
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return {
|
|
367
|
-
locale: context.locale,
|
|
368
|
-
setLocale: (locale: Locale) => {
|
|
369
|
-
if (!locales?.includes(locale)) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
window.history.pushState(null, "", `/${normalize(`${context.prefix ?? ""}/${locale}/${context.path}`)}`);
|
|
374
|
-
window.dispatchEvent(new Event("pushstate"));
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function usePrefix() {
|
|
380
|
-
const context = useContext(RouterContext);
|
|
381
|
-
|
|
382
|
-
if (!context) {
|
|
383
|
-
throw new Error("Component using the usePrefix hook has not been wrapped inside RouterProvider");
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
prefix: context.prefix
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function usePath() {
|
|
392
|
-
const context = useContext(RouterContext);
|
|
393
|
-
|
|
394
|
-
if (!context) {
|
|
395
|
-
throw new Error("Component using the usePath hook has not been wrapped inside RouterProvider");
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
path: context.path,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function useNavigateToPage<P extends Path>(page: Page<P>) {
|
|
404
|
-
const { locale } = useLocale();
|
|
405
|
-
const { prefix } = usePrefix();
|
|
406
|
-
|
|
407
|
-
return useCallback((...[params]: ExtractParams<P> extends never ? [] : [Params<P>]) => {
|
|
408
|
-
const path = Object.entries(params ?? {}).reduce<string>((oldParams, [name, value]) => {
|
|
409
|
-
return oldParams.replace(`:${name}`, String(value));
|
|
410
|
-
}, page.path);
|
|
411
|
-
|
|
412
|
-
const pathname = `/${normalize(`${prefix ?? ""}/${locale ?? ""}/${path}`)}`
|
|
413
|
-
|
|
414
|
-
console.log({ pathname });
|
|
415
|
-
|
|
416
|
-
window.history.pushState(null, "", pathname);
|
|
417
|
-
window.dispatchEvent(new Event("pushstate"));
|
|
418
|
-
}, [page, locale, prefix]);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function RouterProvider({ children }: RouterProviderProps) {
|
|
422
|
-
const uri = useMemo(() => Uri.from(window.location.pathname, expectedPrefix, locales), [expectedPrefix, locales]);
|
|
423
|
-
const [locale, setLocale] = useState(uri.locale);
|
|
424
|
-
const [path, setPath] = useState(uri.path);
|
|
425
|
-
const [prefix, setPrefix] = useState(uri.prefix);
|
|
426
|
-
|
|
427
|
-
function setHash(newHash: string) {
|
|
428
|
-
window.location.hash = newHash;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const value = useMemo(() => {
|
|
432
|
-
return {
|
|
433
|
-
locale,
|
|
434
|
-
path,
|
|
435
|
-
prefix: prefix ?? null,
|
|
436
|
-
setLocale,
|
|
437
|
-
setHash,
|
|
438
|
-
};
|
|
439
|
-
}, [locale, prefix, path]);
|
|
440
|
-
|
|
441
|
-
const onNavigation = useEffectEvent((direction: NavigationDirection) => {
|
|
442
|
-
const pathname = normalize(window.location.pathname);
|
|
443
|
-
const uri = Uri.from(pathname, expectedPrefix, locales);
|
|
444
|
-
|
|
445
|
-
const defaultTransition: Transition = (_, next) => {
|
|
446
|
-
next();
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const currentTransition: Transition = transition ?? defaultTransition;
|
|
450
|
-
|
|
451
|
-
currentTransition(direction, () => {
|
|
452
|
-
setLocale(uri.locale);
|
|
453
|
-
setPath(uri.path);
|
|
454
|
-
setPrefix(uri.prefix);
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
const onMount = useEffectEvent(() => {
|
|
459
|
-
const pathname = `/${normalize(window.location.pathname)}`;
|
|
460
|
-
const uri = Uri.from(pathname, expectedPrefix, [locale, ...locales ?? []]);
|
|
461
|
-
const newPathname = `/${normalize(`${uri.prefix ?? expectedPrefix ?? ""}/${uri.locale ?? locales?.at(0) ?? ""}/${uri.path}`)}`;
|
|
462
|
-
|
|
463
|
-
if (newPathname !== pathname) {
|
|
464
|
-
window.history.pushState(null, "", newPathname);
|
|
465
|
-
window.dispatchEvent(new Event("pushstate"));
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
useEffect(() => {
|
|
470
|
-
const abortController = new AbortController();
|
|
471
|
-
|
|
472
|
-
window.addEventListener("pushstate", () => {
|
|
473
|
-
onNavigation("forward");
|
|
474
|
-
}, abortController);
|
|
475
|
-
|
|
476
|
-
window.addEventListener("popstate", () => {
|
|
477
|
-
onNavigation("backward");
|
|
478
|
-
}, abortController);
|
|
479
|
-
|
|
480
|
-
onMount();
|
|
481
|
-
|
|
482
|
-
return () => {
|
|
483
|
-
abortController.abort();
|
|
484
|
-
};
|
|
485
|
-
}, []);
|
|
486
|
-
|
|
487
|
-
return (
|
|
488
|
-
<RouterContext.Provider value={value}>
|
|
489
|
-
{children}
|
|
490
|
-
</RouterContext.Provider>
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function RouterView() {
|
|
495
|
-
const { path } = usePath();
|
|
496
|
-
|
|
497
|
-
const foundPage = useMemo(() => {
|
|
498
|
-
return pages.find(page => {
|
|
499
|
-
return matchPath(page.path, path);
|
|
500
|
-
});
|
|
501
|
-
}, [path, pages]);
|
|
502
|
-
|
|
503
|
-
if (foundPage) {
|
|
504
|
-
return (
|
|
505
|
-
<ErrorBoundary issue={Issue}>
|
|
506
|
-
<foundPage.element parameters={matchParameters(foundPage.path, path) as Params<Path>} />
|
|
507
|
-
</ErrorBoundary>
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return (
|
|
512
|
-
<ErrorBoundary issue={Issue}>
|
|
513
|
-
<Fallback />
|
|
514
|
-
</ErrorBoundary>
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return {
|
|
519
|
-
RouterProvider,
|
|
520
|
-
RouterView,
|
|
521
|
-
useLocale,
|
|
522
|
-
usePrefix,
|
|
523
|
-
usePath,
|
|
524
|
-
useNavigateToPage,
|
|
525
|
-
useIsActivePage,
|
|
526
|
-
};
|
|
527
|
-
}
|