@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.
@@ -1,209 +1,209 @@
1
- /**
2
- * Mandu Link Component ๐Ÿ”—
3
- * Client-side ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์œ„ํ•œ Link ์ปดํฌ๋„ŒํŠธ
4
- */
5
-
6
- import React, {
7
- type AnchorHTMLAttributes,
8
- type MouseEvent,
9
- type ReactNode,
10
- useCallback,
11
- useEffect,
12
- useRef,
13
- } from "react";
14
- import { navigate, prefetch } from "./router";
15
-
16
- export interface LinkProps
17
- extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
18
- /** ์ด๋™ํ•  URL */
19
- href: string;
20
- /** history.replaceState ์‚ฌ์šฉ ์—ฌ๋ถ€ */
21
- replace?: boolean;
22
- /** ๋งˆ์šฐ์Šค hover ์‹œ prefetch ์—ฌ๋ถ€ */
23
- prefetch?: boolean;
24
- /** ์Šคํฌ๋กค ์œ„์น˜ ๋ณต์› ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) */
25
- scroll?: boolean;
26
- /** ์ž์‹ ์š”์†Œ */
27
- children?: ReactNode;
28
- }
29
-
30
- /**
31
- * Client-side ๋„ค๋น„๊ฒŒ์ด์…˜ Link ์ปดํฌ๋„ŒํŠธ
32
- *
33
- * @example
34
- * ```tsx
35
- * import { Link } from "@mandujs/core/client";
36
- *
37
- * // ๊ธฐ๋ณธ ์‚ฌ์šฉ
38
- * <Link href="/about">About</Link>
39
- *
40
- * // Prefetch ํ™œ์„ฑํ™”
41
- * <Link href="/users" prefetch>Users</Link>
42
- *
43
- * // Replace ๋ชจ๋“œ (๋’ค๋กœ๊ฐ€๊ธฐ ํžˆ์Šคํ† ๋ฆฌ ์—†์Œ)
44
- * <Link href="/login" replace>Login</Link>
45
- * ```
46
- */
47
- export function Link({
48
- href,
49
- replace = false,
50
- prefetch: shouldPrefetch = false,
51
- scroll = true,
52
- children,
53
- onClick,
54
- onMouseEnter,
55
- onFocus,
56
- ...rest
57
- }: LinkProps): React.ReactElement {
58
- const prefetchedRef = useRef(false);
59
-
60
- // ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ
61
- const handleClick = useCallback(
62
- (event: MouseEvent<HTMLAnchorElement>) => {
63
- // ์‚ฌ์šฉ์ž ์ •์˜ onClick ๋จผ์ € ์‹คํ–‰
64
- onClick?.(event);
65
-
66
- // ๊ธฐ๋ณธ ๋™์ž‘ ๋ฐฉ์ง€ ์กฐ๊ฑด
67
- if (
68
- event.defaultPrevented ||
69
- event.button !== 0 ||
70
- event.metaKey ||
71
- event.altKey ||
72
- event.ctrlKey ||
73
- event.shiftKey
74
- ) {
75
- return;
76
- }
77
-
78
- // ์™ธ๋ถ€ ๋งํฌ ์ฒดํฌ
79
- try {
80
- const url = new URL(href, window.location.origin);
81
- if (url.origin !== window.location.origin) {
82
- return; // ์™ธ๋ถ€ ๋งํฌ๋Š” ๊ธฐ๋ณธ ๋™์ž‘
83
- }
84
- } catch {
85
- return;
86
- }
87
-
88
- // Client-side ๋„ค๋น„๊ฒŒ์ด์…˜
89
- event.preventDefault();
90
- navigate(href, { replace, scroll });
91
- },
92
- [href, replace, scroll, onClick]
93
- );
94
-
95
- // Prefetch ์‹คํ–‰
96
- const doPrefetch = useCallback(() => {
97
- if (!shouldPrefetch || prefetchedRef.current) return;
98
-
99
- try {
100
- const url = new URL(href, window.location.origin);
101
- if (url.origin === window.location.origin) {
102
- prefetch(href);
103
- prefetchedRef.current = true;
104
- }
105
- } catch {
106
- // ๋ฌด์‹œ
107
- }
108
- }, [href, shouldPrefetch]);
109
-
110
- // ๋งˆ์šฐ์Šค hover ํ•ธ๋“ค๋Ÿฌ
111
- const handleMouseEnter = useCallback(
112
- (event: MouseEvent<HTMLAnchorElement>) => {
113
- onMouseEnter?.(event);
114
- doPrefetch();
115
- },
116
- [onMouseEnter, doPrefetch]
117
- );
118
-
119
- // ํฌ์ปค์Šค ํ•ธ๋“ค๋Ÿฌ (ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜)
120
- const handleFocus = useCallback(
121
- (event: React.FocusEvent<HTMLAnchorElement>) => {
122
- onFocus?.(event);
123
- doPrefetch();
124
- },
125
- [onFocus, doPrefetch]
126
- );
127
-
128
- // Viewport ์ง„์ž… ์‹œ prefetch (IntersectionObserver)
129
- useEffect(() => {
130
- if (!shouldPrefetch || typeof IntersectionObserver === "undefined") {
131
- return;
132
- }
133
-
134
- // ref๊ฐ€ ์—†์œผ๋ฉด ๋ฌด์‹œ (SSR)
135
- return;
136
- }, [shouldPrefetch]);
137
-
138
- return (
139
- <a
140
- href={href}
141
- onClick={handleClick}
142
- onMouseEnter={handleMouseEnter}
143
- onFocus={handleFocus}
144
- data-mandu-link=""
145
- {...rest}
146
- >
147
- {children}
148
- </a>
149
- );
150
- }
151
-
152
- /**
153
- * NavLink - ํ˜„์žฌ ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•  ๋•Œ ํ™œ์„ฑ ์Šคํƒ€์ผ ์ ์šฉ
154
- *
155
- * @example
156
- * ```tsx
157
- * import { NavLink } from "@mandujs/core/client";
158
- *
159
- * <NavLink
160
- * href="/about"
161
- * className={({ isActive }) => isActive ? "active" : ""}
162
- * >
163
- * About
164
- * </NavLink>
165
- * ```
166
- */
167
- export interface NavLinkProps extends Omit<LinkProps, "className" | "style"> {
168
- /** ํ™œ์„ฑ ์ƒํƒœ์— ๋”ฐ๋ฅธ className */
169
- className?: string | ((props: { isActive: boolean }) => string);
170
- /** ํ™œ์„ฑ ์ƒํƒœ์— ๋”ฐ๋ฅธ style */
171
- style?:
172
- | React.CSSProperties
173
- | ((props: { isActive: boolean }) => React.CSSProperties);
174
- /** ์ •ํ™•ํžˆ ์ผ์น˜ํ•ด์•ผ ํ™œ์„ฑํ™” (๊ธฐ๋ณธ: false) */
175
- exact?: boolean;
176
- }
177
-
178
- export function NavLink({
179
- href,
180
- className,
181
- style,
182
- exact = false,
183
- ...rest
184
- }: NavLinkProps): React.ReactElement {
185
- // ํ˜„์žฌ ๊ฒฝ๋กœ์™€ ๋น„๊ต
186
- const isActive =
187
- typeof window !== "undefined"
188
- ? exact
189
- ? window.location.pathname === href
190
- : window.location.pathname.startsWith(href)
191
- : false;
192
-
193
- const resolvedClassName =
194
- typeof className === "function" ? className({ isActive }) : className;
195
-
196
- const resolvedStyle =
197
- typeof style === "function" ? style({ isActive }) : style;
198
-
199
- return (
200
- <Link
201
- href={href}
202
- className={resolvedClassName}
203
- style={resolvedStyle}
204
- {...rest}
205
- />
206
- );
207
- }
208
-
209
- export default Link;
1
+ /**
2
+ * Mandu Link Component ๐Ÿ”—
3
+ * Client-side ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์œ„ํ•œ Link ์ปดํฌ๋„ŒํŠธ
4
+ */
5
+
6
+ import React, {
7
+ type AnchorHTMLAttributes,
8
+ type MouseEvent,
9
+ type ReactNode,
10
+ useCallback,
11
+ useEffect,
12
+ useRef,
13
+ } from "react";
14
+ import { navigate, prefetch } from "./router";
15
+
16
+ export interface LinkProps
17
+ extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
18
+ /** ์ด๋™ํ•  URL */
19
+ href: string;
20
+ /** history.replaceState ์‚ฌ์šฉ ์—ฌ๋ถ€ */
21
+ replace?: boolean;
22
+ /** ๋งˆ์šฐ์Šค hover ์‹œ prefetch ์—ฌ๋ถ€ */
23
+ prefetch?: boolean;
24
+ /** ์Šคํฌ๋กค ์œ„์น˜ ๋ณต์› ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) */
25
+ scroll?: boolean;
26
+ /** ์ž์‹ ์š”์†Œ */
27
+ children?: ReactNode;
28
+ }
29
+
30
+ /**
31
+ * Client-side ๋„ค๋น„๊ฒŒ์ด์…˜ Link ์ปดํฌ๋„ŒํŠธ
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * import { Link } from "@mandujs/core/client";
36
+ *
37
+ * // ๊ธฐ๋ณธ ์‚ฌ์šฉ
38
+ * <Link href="/about">About</Link>
39
+ *
40
+ * // Prefetch ํ™œ์„ฑํ™”
41
+ * <Link href="/users" prefetch>Users</Link>
42
+ *
43
+ * // Replace ๋ชจ๋“œ (๋’ค๋กœ๊ฐ€๊ธฐ ํžˆ์Šคํ† ๋ฆฌ ์—†์Œ)
44
+ * <Link href="/login" replace>Login</Link>
45
+ * ```
46
+ */
47
+ export function Link({
48
+ href,
49
+ replace = false,
50
+ prefetch: shouldPrefetch = false,
51
+ scroll = true,
52
+ children,
53
+ onClick,
54
+ onMouseEnter,
55
+ onFocus,
56
+ ...rest
57
+ }: LinkProps): React.ReactElement {
58
+ const prefetchedRef = useRef(false);
59
+
60
+ // ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ
61
+ const handleClick = useCallback(
62
+ (event: MouseEvent<HTMLAnchorElement>) => {
63
+ // ์‚ฌ์šฉ์ž ์ •์˜ onClick ๋จผ์ € ์‹คํ–‰
64
+ onClick?.(event);
65
+
66
+ // ๊ธฐ๋ณธ ๋™์ž‘ ๋ฐฉ์ง€ ์กฐ๊ฑด
67
+ if (
68
+ event.defaultPrevented ||
69
+ event.button !== 0 ||
70
+ event.metaKey ||
71
+ event.altKey ||
72
+ event.ctrlKey ||
73
+ event.shiftKey
74
+ ) {
75
+ return;
76
+ }
77
+
78
+ // ์™ธ๋ถ€ ๋งํฌ ์ฒดํฌ
79
+ try {
80
+ const url = new URL(href, window.location.origin);
81
+ if (url.origin !== window.location.origin) {
82
+ return; // ์™ธ๋ถ€ ๋งํฌ๋Š” ๊ธฐ๋ณธ ๋™์ž‘
83
+ }
84
+ } catch {
85
+ return;
86
+ }
87
+
88
+ // Client-side ๋„ค๋น„๊ฒŒ์ด์…˜
89
+ event.preventDefault();
90
+ navigate(href, { replace, scroll });
91
+ },
92
+ [href, replace, scroll, onClick]
93
+ );
94
+
95
+ // Prefetch ์‹คํ–‰
96
+ const doPrefetch = useCallback(() => {
97
+ if (!shouldPrefetch || prefetchedRef.current) return;
98
+
99
+ try {
100
+ const url = new URL(href, window.location.origin);
101
+ if (url.origin === window.location.origin) {
102
+ prefetch(href);
103
+ prefetchedRef.current = true;
104
+ }
105
+ } catch {
106
+ // ๋ฌด์‹œ
107
+ }
108
+ }, [href, shouldPrefetch]);
109
+
110
+ // ๋งˆ์šฐ์Šค hover ํ•ธ๋“ค๋Ÿฌ
111
+ const handleMouseEnter = useCallback(
112
+ (event: MouseEvent<HTMLAnchorElement>) => {
113
+ onMouseEnter?.(event);
114
+ doPrefetch();
115
+ },
116
+ [onMouseEnter, doPrefetch]
117
+ );
118
+
119
+ // ํฌ์ปค์Šค ํ•ธ๋“ค๋Ÿฌ (ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜)
120
+ const handleFocus = useCallback(
121
+ (event: React.FocusEvent<HTMLAnchorElement>) => {
122
+ onFocus?.(event);
123
+ doPrefetch();
124
+ },
125
+ [onFocus, doPrefetch]
126
+ );
127
+
128
+ // Viewport ์ง„์ž… ์‹œ prefetch (IntersectionObserver)
129
+ useEffect(() => {
130
+ if (!shouldPrefetch || typeof IntersectionObserver === "undefined") {
131
+ return;
132
+ }
133
+
134
+ // ref๊ฐ€ ์—†์œผ๋ฉด ๋ฌด์‹œ (SSR)
135
+ return;
136
+ }, [shouldPrefetch]);
137
+
138
+ return (
139
+ <a
140
+ href={href}
141
+ onClick={handleClick}
142
+ onMouseEnter={handleMouseEnter}
143
+ onFocus={handleFocus}
144
+ data-mandu-link=""
145
+ {...rest}
146
+ >
147
+ {children}
148
+ </a>
149
+ );
150
+ }
151
+
152
+ /**
153
+ * NavLink - ํ˜„์žฌ ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•  ๋•Œ ํ™œ์„ฑ ์Šคํƒ€์ผ ์ ์šฉ
154
+ *
155
+ * @example
156
+ * ```tsx
157
+ * import { NavLink } from "@mandujs/core/client";
158
+ *
159
+ * <NavLink
160
+ * href="/about"
161
+ * className={({ isActive }) => isActive ? "active" : ""}
162
+ * >
163
+ * About
164
+ * </NavLink>
165
+ * ```
166
+ */
167
+ export interface NavLinkProps extends Omit<LinkProps, "className" | "style"> {
168
+ /** ํ™œ์„ฑ ์ƒํƒœ์— ๋”ฐ๋ฅธ className */
169
+ className?: string | ((props: { isActive: boolean }) => string);
170
+ /** ํ™œ์„ฑ ์ƒํƒœ์— ๋”ฐ๋ฅธ style */
171
+ style?:
172
+ | React.CSSProperties
173
+ | ((props: { isActive: boolean }) => React.CSSProperties);
174
+ /** ์ •ํ™•ํžˆ ์ผ์น˜ํ•ด์•ผ ํ™œ์„ฑํ™” (๊ธฐ๋ณธ: false) */
175
+ exact?: boolean;
176
+ }
177
+
178
+ export function NavLink({
179
+ href,
180
+ className,
181
+ style,
182
+ exact = false,
183
+ ...rest
184
+ }: NavLinkProps): React.ReactElement {
185
+ // ํ˜„์žฌ ๊ฒฝ๋กœ์™€ ๋น„๊ต
186
+ const isActive =
187
+ typeof window !== "undefined"
188
+ ? exact
189
+ ? window.location.pathname === href
190
+ : window.location.pathname.startsWith(href)
191
+ : false;
192
+
193
+ const resolvedClassName =
194
+ typeof className === "function" ? className({ isActive }) : className;
195
+
196
+ const resolvedStyle =
197
+ typeof style === "function" ? style({ isActive }) : style;
198
+
199
+ return (
200
+ <Link
201
+ href={href}
202
+ className={resolvedClassName}
203
+ style={resolvedStyle}
204
+ {...rest}
205
+ />
206
+ );
207
+ }
208
+
209
+ export default Link;