@mandujs/core 0.9.2 โ 0.9.4
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.ko.md +200 -200
- package/README.md +200 -200
- package/package.json +52 -52
- package/src/bundler/build.ts +6 -0
- package/src/client/Link.tsx +209 -209
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +2 -1
- package/src/client/router.ts +387 -387
- package/src/client/serialize.ts +404 -404
- package/src/contract/client.test.ts +308 -0
- package/src/contract/client.ts +345 -0
- package/src/contract/handler.ts +270 -0
- package/src/contract/index.ts +137 -1
- package/src/contract/infer.test.ts +346 -0
- package/src/contract/types.ts +83 -0
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +438 -438
- package/src/filling/filling.ts +5 -1
- package/src/filling/index.ts +1 -1
- package/src/generator/index.ts +3 -3
- package/src/index.ts +75 -0
- package/src/report/index.ts +1 -1
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/index.ts +3 -3
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/ssr.ts +321 -321
- package/src/runtime/trace.ts +144 -144
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
package/src/client/hooks.ts
CHANGED
|
@@ -1,267 +1,267 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu Router Hooks ๐ช
|
|
3
|
-
* React hooks for client-side routing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useEffect, useCallback, useSyncExternalStore } from "react";
|
|
7
|
-
import {
|
|
8
|
-
subscribe,
|
|
9
|
-
getRouterState,
|
|
10
|
-
getCurrentRoute,
|
|
11
|
-
getLoaderData,
|
|
12
|
-
getNavigationState,
|
|
13
|
-
navigate,
|
|
14
|
-
type RouteInfo,
|
|
15
|
-
type NavigationState,
|
|
16
|
-
type NavigateOptions,
|
|
17
|
-
} from "./router";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* ๋ผ์ฐํฐ ์ํ ์ ์ฒด ์ ๊ทผ
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```tsx
|
|
24
|
-
* const { currentRoute, loaderData, navigation } = useRouterState();
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
export function useRouterState() {
|
|
28
|
-
return useSyncExternalStore(
|
|
29
|
-
subscribe,
|
|
30
|
-
getRouterState,
|
|
31
|
-
getRouterState // SSR์์๋ ๋์ผ
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* ํ์ฌ ๋ผ์ฐํธ ์ ๋ณด
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* ```tsx
|
|
40
|
-
* const route = useRoute();
|
|
41
|
-
* console.log(route?.id, route?.params);
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export function useRoute(): RouteInfo | null {
|
|
45
|
-
const state = useRouterState();
|
|
46
|
-
return state.currentRoute;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* URL ํ๋ผ๋ฏธํฐ ์ ๊ทผ
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```tsx
|
|
54
|
-
* // URL: /users/123
|
|
55
|
-
* const { id } = useParams<{ id: string }>();
|
|
56
|
-
* console.log(id); // "123"
|
|
57
|
-
* ```
|
|
58
|
-
*/
|
|
59
|
-
export function useParams<T extends Record<string, string> = Record<string, string>>(): T {
|
|
60
|
-
const route = useRoute();
|
|
61
|
-
return (route?.params ?? {}) as T;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* ํ์ฌ ๊ฒฝ๋ก๋ช
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```tsx
|
|
69
|
-
* const pathname = usePathname();
|
|
70
|
-
* console.log(pathname); // "/users/123"
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export function usePathname(): string {
|
|
74
|
-
const [pathname, setPathname] = useState(() =>
|
|
75
|
-
typeof window !== "undefined" ? window.location.pathname : "/"
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
const handleChange = () => {
|
|
80
|
-
setPathname(window.location.pathname);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
window.addEventListener("popstate", handleChange);
|
|
84
|
-
|
|
85
|
-
// ๋ผ์ฐํฐ ์ํ ๋ณ๊ฒฝ ๊ตฌ๋
|
|
86
|
-
const unsubscribe = subscribe(() => {
|
|
87
|
-
setPathname(window.location.pathname);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return () => {
|
|
91
|
-
window.removeEventListener("popstate", handleChange);
|
|
92
|
-
unsubscribe();
|
|
93
|
-
};
|
|
94
|
-
}, []);
|
|
95
|
-
|
|
96
|
-
return pathname;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* ํ์ฌ ๊ฒ์ ํ๋ผ๋ฏธํฐ (์ฟผ๋ฆฌ ์คํธ๋ง)
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```tsx
|
|
104
|
-
* // URL: /search?q=hello&page=2
|
|
105
|
-
* const searchParams = useSearchParams();
|
|
106
|
-
* console.log(searchParams.get("q")); // "hello"
|
|
107
|
-
* ```
|
|
108
|
-
*/
|
|
109
|
-
export function useSearchParams(): URLSearchParams {
|
|
110
|
-
const [searchParams, setSearchParams] = useState(() =>
|
|
111
|
-
typeof window !== "undefined"
|
|
112
|
-
? new URLSearchParams(window.location.search)
|
|
113
|
-
: new URLSearchParams()
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
const handleChange = () => {
|
|
118
|
-
setSearchParams(new URLSearchParams(window.location.search));
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
window.addEventListener("popstate", handleChange);
|
|
122
|
-
|
|
123
|
-
const unsubscribe = subscribe(() => {
|
|
124
|
-
setSearchParams(new URLSearchParams(window.location.search));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
return () => {
|
|
128
|
-
window.removeEventListener("popstate", handleChange);
|
|
129
|
-
unsubscribe();
|
|
130
|
-
};
|
|
131
|
-
}, []);
|
|
132
|
-
|
|
133
|
-
return searchParams;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Loader ๋ฐ์ดํฐ ์ ๊ทผ
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```tsx
|
|
141
|
-
* interface UserData { name: string; email: string; }
|
|
142
|
-
* const data = useLoaderData<UserData>();
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
export function useLoaderData<T = unknown>(): T | undefined {
|
|
146
|
-
const state = useRouterState();
|
|
147
|
-
return state.loaderData as T | undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* ๋ค๋น๊ฒ์ด์
์ํ (๋ก๋ฉ ์ฌ๋ถ)
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```tsx
|
|
155
|
-
* const { state, location } = useNavigation();
|
|
156
|
-
*
|
|
157
|
-
* if (state === "loading") {
|
|
158
|
-
* return <Spinner />;
|
|
159
|
-
* }
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
export function useNavigation(): NavigationState {
|
|
163
|
-
const state = useRouterState();
|
|
164
|
-
return state.navigation;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* ํ๋ก๊ทธ๋๋งคํฑ ๋ค๋น๊ฒ์ด์
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```tsx
|
|
172
|
-
* const navigate = useNavigate();
|
|
173
|
-
*
|
|
174
|
-
* const handleClick = () => {
|
|
175
|
-
* navigate("/dashboard");
|
|
176
|
-
* };
|
|
177
|
-
*
|
|
178
|
-
* const handleSubmit = () => {
|
|
179
|
-
* navigate("/success", { replace: true });
|
|
180
|
-
* };
|
|
181
|
-
* ```
|
|
182
|
-
*/
|
|
183
|
-
export function useNavigate(): (to: string, options?: NavigateOptions) => Promise<void> {
|
|
184
|
-
return useCallback((to: string, options?: NavigateOptions) => {
|
|
185
|
-
return navigate(to, options);
|
|
186
|
-
}, []);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* ๋ผ์ฐํฐ ํตํฉ ํ
(ํธ์์ฉ)
|
|
191
|
-
*
|
|
192
|
-
* @example
|
|
193
|
-
* ```tsx
|
|
194
|
-
* const {
|
|
195
|
-
* pathname,
|
|
196
|
-
* params,
|
|
197
|
-
* searchParams,
|
|
198
|
-
* navigate,
|
|
199
|
-
* isNavigating
|
|
200
|
-
* } = useRouter();
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
export function useRouter() {
|
|
204
|
-
const pathname = usePathname();
|
|
205
|
-
const params = useParams();
|
|
206
|
-
const searchParams = useSearchParams();
|
|
207
|
-
const navigation = useNavigation();
|
|
208
|
-
const navigateFn = useNavigate();
|
|
209
|
-
|
|
210
|
-
return {
|
|
211
|
-
/** ํ์ฌ ๊ฒฝ๋ก๋ช
*/
|
|
212
|
-
pathname,
|
|
213
|
-
/** URL ํ๋ผ๋ฏธํฐ */
|
|
214
|
-
params,
|
|
215
|
-
/** ๊ฒ์ ํ๋ผ๋ฏธํฐ (์ฟผ๋ฆฌ ์คํธ๋ง) */
|
|
216
|
-
searchParams,
|
|
217
|
-
/** ๋ค๋น๊ฒ์ด์
ํจ์ */
|
|
218
|
-
navigate: navigateFn,
|
|
219
|
-
/** ๋ค๋น๊ฒ์ด์
์ค ์ฌ๋ถ */
|
|
220
|
-
isNavigating: navigation.state === "loading",
|
|
221
|
-
/** ๋ค๋น๊ฒ์ด์
์ํ ์์ธ */
|
|
222
|
-
navigation,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* ํน์ ๊ฒฝ๋ก์ ํ์ฌ ๊ฒฝ๋ก ์ผ์น ์ฌ๋ถ
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* ```tsx
|
|
231
|
-
* const isActive = useMatch("/about");
|
|
232
|
-
* const isUsersPage = useMatch("/users/:id");
|
|
233
|
-
* ```
|
|
234
|
-
*/
|
|
235
|
-
export function useMatch(pattern: string): boolean {
|
|
236
|
-
const pathname = usePathname();
|
|
237
|
-
|
|
238
|
-
// ๊ฐ๋จํ ํจํด ๋งค์นญ (ํ๋ผ๋ฏธํฐ ๊ณ ๋ ค)
|
|
239
|
-
const regexStr = pattern
|
|
240
|
-
.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "[^/]+")
|
|
241
|
-
.replace(/\//g, "\\/");
|
|
242
|
-
|
|
243
|
-
const regex = new RegExp(`^${regexStr}$`);
|
|
244
|
-
return regex.test(pathname);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* ๋ค๋ก ๊ฐ๊ธฐ
|
|
249
|
-
*/
|
|
250
|
-
export function useGoBack(): () => void {
|
|
251
|
-
return useCallback(() => {
|
|
252
|
-
if (typeof window !== "undefined") {
|
|
253
|
-
window.history.back();
|
|
254
|
-
}
|
|
255
|
-
}, []);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* ์์ผ๋ก ๊ฐ๊ธฐ
|
|
260
|
-
*/
|
|
261
|
-
export function useGoForward(): () => void {
|
|
262
|
-
return useCallback(() => {
|
|
263
|
-
if (typeof window !== "undefined") {
|
|
264
|
-
window.history.forward();
|
|
265
|
-
}
|
|
266
|
-
}, []);
|
|
267
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Router Hooks ๐ช
|
|
3
|
+
* React hooks for client-side routing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback, useSyncExternalStore } from "react";
|
|
7
|
+
import {
|
|
8
|
+
subscribe,
|
|
9
|
+
getRouterState,
|
|
10
|
+
getCurrentRoute,
|
|
11
|
+
getLoaderData,
|
|
12
|
+
getNavigationState,
|
|
13
|
+
navigate,
|
|
14
|
+
type RouteInfo,
|
|
15
|
+
type NavigationState,
|
|
16
|
+
type NavigateOptions,
|
|
17
|
+
} from "./router";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ๋ผ์ฐํฐ ์ํ ์ ์ฒด ์ ๊ทผ
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const { currentRoute, loaderData, navigation } = useRouterState();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function useRouterState() {
|
|
28
|
+
return useSyncExternalStore(
|
|
29
|
+
subscribe,
|
|
30
|
+
getRouterState,
|
|
31
|
+
getRouterState // SSR์์๋ ๋์ผ
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ํ์ฌ ๋ผ์ฐํธ ์ ๋ณด
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const route = useRoute();
|
|
41
|
+
* console.log(route?.id, route?.params);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useRoute(): RouteInfo | null {
|
|
45
|
+
const state = useRouterState();
|
|
46
|
+
return state.currentRoute;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* URL ํ๋ผ๋ฏธํฐ ์ ๊ทผ
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* // URL: /users/123
|
|
55
|
+
* const { id } = useParams<{ id: string }>();
|
|
56
|
+
* console.log(id); // "123"
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function useParams<T extends Record<string, string> = Record<string, string>>(): T {
|
|
60
|
+
const route = useRoute();
|
|
61
|
+
return (route?.params ?? {}) as T;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* ํ์ฌ ๊ฒฝ๋ก๋ช
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```tsx
|
|
69
|
+
* const pathname = usePathname();
|
|
70
|
+
* console.log(pathname); // "/users/123"
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function usePathname(): string {
|
|
74
|
+
const [pathname, setPathname] = useState(() =>
|
|
75
|
+
typeof window !== "undefined" ? window.location.pathname : "/"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const handleChange = () => {
|
|
80
|
+
setPathname(window.location.pathname);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
window.addEventListener("popstate", handleChange);
|
|
84
|
+
|
|
85
|
+
// ๋ผ์ฐํฐ ์ํ ๋ณ๊ฒฝ ๊ตฌ๋
|
|
86
|
+
const unsubscribe = subscribe(() => {
|
|
87
|
+
setPathname(window.location.pathname);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return () => {
|
|
91
|
+
window.removeEventListener("popstate", handleChange);
|
|
92
|
+
unsubscribe();
|
|
93
|
+
};
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
return pathname;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* ํ์ฌ ๊ฒ์ ํ๋ผ๋ฏธํฐ (์ฟผ๋ฆฌ ์คํธ๋ง)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```tsx
|
|
104
|
+
* // URL: /search?q=hello&page=2
|
|
105
|
+
* const searchParams = useSearchParams();
|
|
106
|
+
* console.log(searchParams.get("q")); // "hello"
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function useSearchParams(): URLSearchParams {
|
|
110
|
+
const [searchParams, setSearchParams] = useState(() =>
|
|
111
|
+
typeof window !== "undefined"
|
|
112
|
+
? new URLSearchParams(window.location.search)
|
|
113
|
+
: new URLSearchParams()
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const handleChange = () => {
|
|
118
|
+
setSearchParams(new URLSearchParams(window.location.search));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
window.addEventListener("popstate", handleChange);
|
|
122
|
+
|
|
123
|
+
const unsubscribe = subscribe(() => {
|
|
124
|
+
setSearchParams(new URLSearchParams(window.location.search));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return () => {
|
|
128
|
+
window.removeEventListener("popstate", handleChange);
|
|
129
|
+
unsubscribe();
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return searchParams;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Loader ๋ฐ์ดํฐ ์ ๊ทผ
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```tsx
|
|
141
|
+
* interface UserData { name: string; email: string; }
|
|
142
|
+
* const data = useLoaderData<UserData>();
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function useLoaderData<T = unknown>(): T | undefined {
|
|
146
|
+
const state = useRouterState();
|
|
147
|
+
return state.loaderData as T | undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* ๋ค๋น๊ฒ์ด์
์ํ (๋ก๋ฉ ์ฌ๋ถ)
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```tsx
|
|
155
|
+
* const { state, location } = useNavigation();
|
|
156
|
+
*
|
|
157
|
+
* if (state === "loading") {
|
|
158
|
+
* return <Spinner />;
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function useNavigation(): NavigationState {
|
|
163
|
+
const state = useRouterState();
|
|
164
|
+
return state.navigation;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* ํ๋ก๊ทธ๋๋งคํฑ ๋ค๋น๊ฒ์ด์
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```tsx
|
|
172
|
+
* const navigate = useNavigate();
|
|
173
|
+
*
|
|
174
|
+
* const handleClick = () => {
|
|
175
|
+
* navigate("/dashboard");
|
|
176
|
+
* };
|
|
177
|
+
*
|
|
178
|
+
* const handleSubmit = () => {
|
|
179
|
+
* navigate("/success", { replace: true });
|
|
180
|
+
* };
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function useNavigate(): (to: string, options?: NavigateOptions) => Promise<void> {
|
|
184
|
+
return useCallback((to: string, options?: NavigateOptions) => {
|
|
185
|
+
return navigate(to, options);
|
|
186
|
+
}, []);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* ๋ผ์ฐํฐ ํตํฉ ํ
(ํธ์์ฉ)
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```tsx
|
|
194
|
+
* const {
|
|
195
|
+
* pathname,
|
|
196
|
+
* params,
|
|
197
|
+
* searchParams,
|
|
198
|
+
* navigate,
|
|
199
|
+
* isNavigating
|
|
200
|
+
* } = useRouter();
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function useRouter() {
|
|
204
|
+
const pathname = usePathname();
|
|
205
|
+
const params = useParams();
|
|
206
|
+
const searchParams = useSearchParams();
|
|
207
|
+
const navigation = useNavigation();
|
|
208
|
+
const navigateFn = useNavigate();
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
/** ํ์ฌ ๊ฒฝ๋ก๋ช
*/
|
|
212
|
+
pathname,
|
|
213
|
+
/** URL ํ๋ผ๋ฏธํฐ */
|
|
214
|
+
params,
|
|
215
|
+
/** ๊ฒ์ ํ๋ผ๋ฏธํฐ (์ฟผ๋ฆฌ ์คํธ๋ง) */
|
|
216
|
+
searchParams,
|
|
217
|
+
/** ๋ค๋น๊ฒ์ด์
ํจ์ */
|
|
218
|
+
navigate: navigateFn,
|
|
219
|
+
/** ๋ค๋น๊ฒ์ด์
์ค ์ฌ๋ถ */
|
|
220
|
+
isNavigating: navigation.state === "loading",
|
|
221
|
+
/** ๋ค๋น๊ฒ์ด์
์ํ ์์ธ */
|
|
222
|
+
navigation,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* ํน์ ๊ฒฝ๋ก์ ํ์ฌ ๊ฒฝ๋ก ์ผ์น ์ฌ๋ถ
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```tsx
|
|
231
|
+
* const isActive = useMatch("/about");
|
|
232
|
+
* const isUsersPage = useMatch("/users/:id");
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
export function useMatch(pattern: string): boolean {
|
|
236
|
+
const pathname = usePathname();
|
|
237
|
+
|
|
238
|
+
// ๊ฐ๋จํ ํจํด ๋งค์นญ (ํ๋ผ๋ฏธํฐ ๊ณ ๋ ค)
|
|
239
|
+
const regexStr = pattern
|
|
240
|
+
.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "[^/]+")
|
|
241
|
+
.replace(/\//g, "\\/");
|
|
242
|
+
|
|
243
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
244
|
+
return regex.test(pathname);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* ๋ค๋ก ๊ฐ๊ธฐ
|
|
249
|
+
*/
|
|
250
|
+
export function useGoBack(): () => void {
|
|
251
|
+
return useCallback(() => {
|
|
252
|
+
if (typeof window !== "undefined") {
|
|
253
|
+
window.history.back();
|
|
254
|
+
}
|
|
255
|
+
}, []);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* ์์ผ๋ก ๊ฐ๊ธฐ
|
|
260
|
+
*/
|
|
261
|
+
export function useGoForward(): () => void {
|
|
262
|
+
return useCallback(() => {
|
|
263
|
+
if (typeof window !== "undefined") {
|
|
264
|
+
window.history.forward();
|
|
265
|
+
}
|
|
266
|
+
}, []);
|
|
267
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -103,8 +103,9 @@ import { Link, NavLink } from "./Link";
|
|
|
103
103
|
/**
|
|
104
104
|
* Mandu Client namespace
|
|
105
105
|
* v0.8.0: Hydration์ ์๋์ผ๋ก ์ฒ๋ฆฌ๋จ (generateRuntimeSource์์ ์์ฑ)
|
|
106
|
+
* Note: Use `ManduClient` to avoid conflict with other Mandu exports
|
|
106
107
|
*/
|
|
107
|
-
export const
|
|
108
|
+
export const ManduClient = {
|
|
108
109
|
/**
|
|
109
110
|
* Create an island component
|
|
110
111
|
* @see island
|