@musecat/functionkit 1.0.2 → 1.2.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/index.ts +1 -1
- package/package.json +37 -2
- package/packages/components/ScrolltoTop.tsx +1 -1
- package/packages/components/ViewportPortal.tsx +1 -2
- package/packages/cookie/cookie.shared.ts +17 -35
- package/packages/cookie/cookieNames.shared.ts +9 -0
- package/packages/datetime/dateTime.client.ts +1 -3
- package/packages/datetime/dateTime.server.ts +1 -3
- package/packages/datetime/dateTime.shared.ts +9 -47
- package/packages/hooks/useCheckInvisible.ts +1 -1
- package/packages/hooks/useClientDateTime.ts +3 -16
- package/packages/hooks/useDebounce.ts +2 -7
- package/packages/hooks/useGeolocation.ts +1 -5
- package/packages/hooks/useInterval.ts +1 -2
- package/packages/hooks/useKeyboardListNavigation.ts +10 -38
- package/packages/hooks/useLongPress.ts +13 -24
- package/packages/hooks/useRelativeDateTime.ts +1 -4
- package/packages/hooks/useViewportHeight.ts +1 -2
- package/packages/hooks/useViewportMatch.ts +0 -4
- package/packages/utils/buildContext.tsx +2 -3
- package/packages/utils/floatingMotion.ts +15 -23
- package/packages/utils/subscribeKeyboardHeight.ts +3 -0
- package/.agents/references/components/ScrolltoTop.md +0 -36
- package/.agents/references/components/SwitchCase.md +0 -47
- package/.agents/references/components/ViewportPortal.md +0 -34
- package/.agents/references/cookie/cookie.md +0 -47
- package/.agents/references/datetime/dateTime.client.md +0 -42
- package/.agents/references/datetime/dateTime.server.md +0 -42
- package/.agents/references/datetime/dateTime.shared.md +0 -86
- package/.agents/references/hooks/useAvoidKeyboard.md +0 -29
- package/.agents/references/hooks/useCheckInvisible.md +0 -36
- package/.agents/references/hooks/useCheckScroll.md +0 -36
- package/.agents/references/hooks/useClientDateTime.md +0 -39
- package/.agents/references/hooks/useDebounce.md +0 -46
- package/.agents/references/hooks/useDebouncedCallback.md +0 -34
- package/.agents/references/hooks/useDoubleClick.md +0 -40
- package/.agents/references/hooks/useGeolocation.md +0 -37
- package/.agents/references/hooks/useHasMounted.md +0 -27
- package/.agents/references/hooks/useIntersectionObserver.md +0 -37
- package/.agents/references/hooks/useInterval.md +0 -40
- package/.agents/references/hooks/useKeyboardHeight.md +0 -33
- package/.agents/references/hooks/useKeyboardListNavigation.md +0 -58
- package/.agents/references/hooks/useLongPress.md +0 -48
- package/.agents/references/hooks/usePreservedCallback.md +0 -33
- package/.agents/references/hooks/usePreservedReference.md +0 -34
- package/.agents/references/hooks/useRefEffect.md +0 -35
- package/.agents/references/hooks/useRelativeDateTime.md +0 -35
- package/.agents/references/hooks/useTimeout.md +0 -42
- package/.agents/references/hooks/useToggleState.md +0 -39
- package/.agents/references/hooks/useViewportHeight.md +0 -26
- package/.agents/references/hooks/useViewportMatch.md +0 -32
- package/.agents/references/utils/browserStorage.md +0 -41
- package/.agents/references/utils/buildContext.md +0 -47
- package/.agents/references/utils/clipboardShare.md +0 -59
- package/.agents/references/utils/floatingMotion.md +0 -67
- package/.agents/references/utils/getDeviceInfo.md +0 -43
- package/.agents/references/utils/isEditableKeyboardTarget.md +0 -35
- package/.agents/references/utils/mergeRefs.md +0 -29
- package/.agents/references/utils/seen.md +0 -37
- package/.agents/references/utils/subscribeKeyboardHeight.md +0 -35
- package/.gitattributes +0 -2
- package/AGENTS.md +0 -105
- package/tests/components/ScrolltoTop.test.tsx +0 -15
- package/tests/components/SwitchCase.test.tsx +0 -34
- package/tests/components/ViewportPortal.test.tsx +0 -42
- package/tests/cookie/cookie.test.ts +0 -156
- package/tests/datetime/datetime.test.ts +0 -250
- package/tests/hooks/useAvoidKeyboard.test.ts +0 -12
- package/tests/hooks/useCheckInvisible.test.ts +0 -28
- package/tests/hooks/useCheckScroll.test.ts +0 -23
- package/tests/hooks/useClientDateTime.test.ts +0 -14
- package/tests/hooks/useDebounce.test.ts +0 -71
- package/tests/hooks/useDebouncedCallback.test.ts +0 -34
- package/tests/hooks/useDoubleClick.test.ts +0 -31
- package/tests/hooks/useGeolocation.test.ts +0 -50
- package/tests/hooks/useHasMounted.test.ts +0 -10
- package/tests/hooks/useIntersectionObserver.test.ts +0 -13
- package/tests/hooks/useInterval.test.ts +0 -31
- package/tests/hooks/useKeyboardHeight.test.ts +0 -10
- package/tests/hooks/useKeyboardListNavigation.test.ts +0 -82
- package/tests/hooks/useLongPress.test.ts +0 -52
- package/tests/hooks/usePreservedCallback.test.ts +0 -35
- package/tests/hooks/usePreservedReference.test.ts +0 -35
- package/tests/hooks/useRefEffect.test.ts +0 -30
- package/tests/hooks/useRelativeDateTime.test.ts +0 -19
- package/tests/hooks/useTimeout.test.ts +0 -34
- package/tests/hooks/useToggleState.test.ts +0 -35
- package/tests/hooks/useViewportHeight.test.ts +0 -10
- package/tests/hooks/useViewportMatch.test.ts +0 -35
- package/tests/setup.ts +0 -9
- package/tests/utils/browserStorage.test.ts +0 -54
- package/tests/utils/buildContext.test.tsx +0 -27
- package/tests/utils/checkDevice.test.ts +0 -84
- package/tests/utils/clipboardShare.test.ts +0 -78
- package/tests/utils/floatingMotion.test.ts +0 -84
- package/tests/utils/keyboardTarget.test.ts +0 -44
- package/tests/utils/mergeRefs.test.ts +0 -18
- package/tests/utils/seen.test.ts +0 -31
- package/tests/utils/subscribeKeyboardHeight.test.ts +0 -50
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -16
package/index.ts
CHANGED
|
@@ -6,9 +6,9 @@ export {
|
|
|
6
6
|
clearAllClientCookies,
|
|
7
7
|
clearClientCookie,
|
|
8
8
|
getClientCookie,
|
|
9
|
-
parseClientCookieNames,
|
|
10
9
|
setClientCookie,
|
|
11
10
|
} from "./packages/cookie/cookie.shared";
|
|
11
|
+
export { parseClientCookieNames } from "./packages/cookie/cookieNames.shared";
|
|
12
12
|
export {
|
|
13
13
|
formatClientDate,
|
|
14
14
|
formatClientDateTime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@musecat/functionkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "React 19 + TypeScript frontend utility library — hooks, components, datetime, cookie, and utils",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"hooks",
|
|
8
|
+
"typescript",
|
|
9
|
+
"utility",
|
|
10
|
+
"frontend"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"private": false,
|
|
14
|
+
"author": "Musecat Team",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20.0.0"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.ts",
|
|
20
|
+
"packages/",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/TheTechclip/FunctionKit.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/TheTechclip/FunctionKit/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://musecat.app",
|
|
4
33
|
"type": "module",
|
|
5
34
|
"main": "./index.ts",
|
|
6
35
|
"types": "./index.ts",
|
|
@@ -11,7 +40,12 @@
|
|
|
11
40
|
"scripts": {
|
|
12
41
|
"lint": "biome check",
|
|
13
42
|
"format": "prettier --write . && biome format --write",
|
|
14
|
-
"test": "vitest run"
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:coverage": "vitest run --coverage"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"registry": "https://registry.npmjs.org/",
|
|
48
|
+
"access": "public"
|
|
15
49
|
},
|
|
16
50
|
"peerDependencies": {
|
|
17
51
|
"react": "^19.0.0",
|
|
@@ -22,6 +56,7 @@
|
|
|
22
56
|
"@testing-library/jest-dom": "^6.6.3",
|
|
23
57
|
"@testing-library/react": "^16.2.0",
|
|
24
58
|
"@testing-library/user-event": "^14.6.1",
|
|
59
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
25
60
|
"jsdom": "^26.0.0",
|
|
26
61
|
"prettier": "^3.9.5",
|
|
27
62
|
"vitest": "^4.1.10"
|
|
@@ -18,8 +18,7 @@ export function getViewportPortalRoot() {
|
|
|
18
18
|
const root = document.createElement("div");
|
|
19
19
|
root.id = VIEWPORT_PORTAL_ROOT_ID;
|
|
20
20
|
root.setAttribute("data-viewport-portal-root", "true");
|
|
21
|
-
root.style.cssText =
|
|
22
|
-
"position:fixed;inset:0;pointer-events:none;z-index:9999;";
|
|
21
|
+
root.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:9999;";
|
|
23
22
|
document.body.appendChild(root);
|
|
24
23
|
return root;
|
|
25
24
|
}
|
|
@@ -22,10 +22,7 @@ type ClearAllClientCookiesOptions = ClearClientCookieOptions & {
|
|
|
22
22
|
cookieString?: string;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
function findClientCookie(
|
|
26
|
-
name: string,
|
|
27
|
-
documentRef: DocumentCookieRef,
|
|
28
|
-
): string | undefined {
|
|
25
|
+
function findClientCookie(name: string, documentRef: DocumentCookieRef): string | undefined {
|
|
29
26
|
const parsed = parseClientCookie(documentRef.cookie);
|
|
30
27
|
return parsed[name];
|
|
31
28
|
}
|
|
@@ -42,15 +39,9 @@ function parseClientCookie(cookieString: string): Record<string, string> {
|
|
|
42
39
|
}, {});
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.map((entry) => {
|
|
49
|
-
const eqPos = entry.indexOf("=");
|
|
50
|
-
return eqPos > -1 ? entry.slice(0, eqPos).trim() : entry.trim();
|
|
51
|
-
})
|
|
52
|
-
.filter(Boolean);
|
|
53
|
-
}
|
|
42
|
+
import { parseClientCookieNames } from "./cookieNames.shared";
|
|
43
|
+
|
|
44
|
+
export { parseClientCookieNames };
|
|
54
45
|
|
|
55
46
|
export function getClientCookie(name: string): string | undefined {
|
|
56
47
|
if (typeof document === "undefined") {
|
|
@@ -60,11 +51,18 @@ export function getClientCookie(name: string): string | undefined {
|
|
|
60
51
|
return findClientCookie(name, document);
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
export function setClientCookie(
|
|
54
|
+
export function setClientCookie(
|
|
55
|
+
name: string,
|
|
56
|
+
value: string,
|
|
57
|
+
days?: number,
|
|
58
|
+
documentRef?: DocumentCookieRef,
|
|
59
|
+
) {
|
|
64
60
|
if (typeof document === "undefined") {
|
|
65
61
|
return;
|
|
66
62
|
}
|
|
67
63
|
|
|
64
|
+
const ref = documentRef ?? document;
|
|
65
|
+
|
|
68
66
|
let expires = "";
|
|
69
67
|
if (days) {
|
|
70
68
|
const date = new Date();
|
|
@@ -72,31 +70,17 @@ export function setClientCookie(name: string, value: string, days?: number) {
|
|
|
72
70
|
expires = `; expires=${date.toUTCString()}`;
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
ref.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
export function clearClientCookie(
|
|
79
|
-
|
|
80
|
-
options: ClearClientCookieOptions = {},
|
|
81
|
-
): void {
|
|
82
|
-
const {
|
|
83
|
-
hostname = window.location.hostname,
|
|
84
|
-
path = "/",
|
|
85
|
-
documentRef = document,
|
|
86
|
-
} = options;
|
|
76
|
+
export function clearClientCookie(name: string, options: ClearClientCookieOptions = {}): void {
|
|
77
|
+
const { hostname = window.location.hostname, path = "/", documentRef = document } = options;
|
|
87
78
|
|
|
88
79
|
documentRef.cookie = `${name}=; expires=${EXPIRED_COOKIE_DATE}; path=${path}; domain=${hostname};`;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
|
-
export function clearAllClientCookies(
|
|
92
|
-
|
|
93
|
-
): string[] {
|
|
94
|
-
const {
|
|
95
|
-
documentRef = document,
|
|
96
|
-
cookieStore,
|
|
97
|
-
includeRoot = false,
|
|
98
|
-
cookieString,
|
|
99
|
-
} = options;
|
|
82
|
+
export function clearAllClientCookies(options: ClearAllClientCookiesOptions = {}): string[] {
|
|
83
|
+
const { documentRef = document, cookieStore, includeRoot = false, cookieString } = options;
|
|
100
84
|
|
|
101
85
|
const entries = cookieString
|
|
102
86
|
? parseClientCookieNames(cookieString)
|
|
@@ -124,8 +108,6 @@ export function clearAllClientCookies(
|
|
|
124
108
|
}
|
|
125
109
|
|
|
126
110
|
for (const name of entries) {
|
|
127
|
-
if (!name) continue;
|
|
128
|
-
|
|
129
111
|
for (const path of paths) {
|
|
130
112
|
for (let i = 0; i < hostnameParts.length; i++) {
|
|
131
113
|
const domain = hostnameParts.slice(i).join(".");
|
|
@@ -72,9 +72,7 @@ export function formatClientDateTime(
|
|
|
72
72
|
timeZone: options.timeZone,
|
|
73
73
|
preset: options.timePreset ?? "24h-minute",
|
|
74
74
|
});
|
|
75
|
-
return dateText && timeText
|
|
76
|
-
? `${dateText} ${timeText}`
|
|
77
|
-
: dateText || timeText;
|
|
75
|
+
return dateText && timeText ? `${dateText} ${timeText}` : dateText || timeText;
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
export function formatClientRelative(
|
|
@@ -72,9 +72,7 @@ export function formatServerDateTime(
|
|
|
72
72
|
timeZone: options.timeZone,
|
|
73
73
|
preset: options.timePreset ?? "24h-minute",
|
|
74
74
|
});
|
|
75
|
-
return dateText && timeText
|
|
76
|
-
? `${dateText} ${timeText}`
|
|
77
|
-
: dateText || timeText;
|
|
75
|
+
return dateText && timeText ? `${dateText} ${timeText}` : dateText || timeText;
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
export function formatServerRelative(
|
|
@@ -17,8 +17,7 @@ type TimeParts = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export function normalizeAppLocale(locale?: string): AppLocale {
|
|
20
|
-
const normalized =
|
|
21
|
-
typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
20
|
+
const normalized = typeof locale === "string" ? locale.trim().toLowerCase() : "";
|
|
22
21
|
if (normalized === "kr" || normalized === "ko") return "kr";
|
|
23
22
|
if (normalized === "jp" || normalized === "ja") return "jp";
|
|
24
23
|
return "en";
|
|
@@ -45,15 +44,10 @@ export function toDate(value: DateInput): Date | null {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export function toUtcMidnight(date: Date): Date {
|
|
48
|
-
return new Date(
|
|
49
|
-
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
|
|
50
|
-
);
|
|
47
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
export function parseUtcDateInput(
|
|
54
|
-
value?: DateInput | null,
|
|
55
|
-
fallback?: DateInput,
|
|
56
|
-
): Date | null {
|
|
50
|
+
export function parseUtcDateInput(value?: DateInput | null, fallback?: DateInput): Date | null {
|
|
57
51
|
if (typeof value === "string") {
|
|
58
52
|
const trimmed = value.trim();
|
|
59
53
|
const match = DATE_ONLY_PATTERN.exec(trimmed);
|
|
@@ -128,11 +122,7 @@ function getTimeParts(date: Date, timeZone?: string): TimeParts {
|
|
|
128
122
|
};
|
|
129
123
|
}
|
|
130
124
|
|
|
131
|
-
export function formatLongDate(
|
|
132
|
-
date: Date,
|
|
133
|
-
locale?: string,
|
|
134
|
-
timeZone?: string,
|
|
135
|
-
): string {
|
|
125
|
+
export function formatLongDate(date: Date, locale?: string, timeZone?: string): string {
|
|
136
126
|
const localeKey = normalizeAppLocale(locale);
|
|
137
127
|
const { year, month, day } = getDateParts(date, timeZone);
|
|
138
128
|
|
|
@@ -177,11 +167,7 @@ export function formatKoreanTime(date: Date, timeZone?: string): string {
|
|
|
177
167
|
return `${meridiem} ${hour12}시 ${minute}분`;
|
|
178
168
|
}
|
|
179
169
|
|
|
180
|
-
export function formatTwelveHourTime(
|
|
181
|
-
date: Date,
|
|
182
|
-
locale?: string,
|
|
183
|
-
timeZone?: string,
|
|
184
|
-
): string {
|
|
170
|
+
export function formatTwelveHourTime(date: Date, locale?: string, timeZone?: string): string {
|
|
185
171
|
const localeKey = normalizeAppLocale(locale);
|
|
186
172
|
if (localeKey === "kr") {
|
|
187
173
|
const { hour, minute } = getTimeParts(date, timeZone);
|
|
@@ -298,25 +284,13 @@ function formatRelativeUnit(
|
|
|
298
284
|
): string {
|
|
299
285
|
if (locale === "kr") {
|
|
300
286
|
const suffix =
|
|
301
|
-
unit === "second"
|
|
302
|
-
? "초"
|
|
303
|
-
: unit === "minute"
|
|
304
|
-
? "분"
|
|
305
|
-
: unit === "hour"
|
|
306
|
-
? "시간"
|
|
307
|
-
: "일";
|
|
287
|
+
unit === "second" ? "초" : unit === "minute" ? "분" : unit === "hour" ? "시간" : "일";
|
|
308
288
|
return `${count}${suffix} 전`;
|
|
309
289
|
}
|
|
310
290
|
|
|
311
291
|
if (locale === "jp") {
|
|
312
292
|
const suffix =
|
|
313
|
-
unit === "second"
|
|
314
|
-
? "秒"
|
|
315
|
-
: unit === "minute"
|
|
316
|
-
? "分"
|
|
317
|
-
: unit === "hour"
|
|
318
|
-
? "時間"
|
|
319
|
-
: "日";
|
|
293
|
+
unit === "second" ? "秒" : unit === "minute" ? "分" : unit === "hour" ? "時間" : "日";
|
|
320
294
|
return `${count}${suffix}前`;
|
|
321
295
|
}
|
|
322
296
|
|
|
@@ -331,25 +305,13 @@ function formatRemainingUnit(
|
|
|
331
305
|
): string {
|
|
332
306
|
if (locale === "kr") {
|
|
333
307
|
const suffix =
|
|
334
|
-
unit === "second"
|
|
335
|
-
? "초"
|
|
336
|
-
: unit === "minute"
|
|
337
|
-
? "분"
|
|
338
|
-
: unit === "hour"
|
|
339
|
-
? "시간"
|
|
340
|
-
: "일";
|
|
308
|
+
unit === "second" ? "초" : unit === "minute" ? "분" : unit === "hour" ? "시간" : "일";
|
|
341
309
|
return `${count}${suffix}`;
|
|
342
310
|
}
|
|
343
311
|
|
|
344
312
|
if (locale === "jp") {
|
|
345
313
|
const suffix =
|
|
346
|
-
unit === "second"
|
|
347
|
-
? "秒"
|
|
348
|
-
: unit === "minute"
|
|
349
|
-
? "分"
|
|
350
|
-
: unit === "hour"
|
|
351
|
-
? "時間"
|
|
352
|
-
: "日";
|
|
314
|
+
unit === "second" ? "秒" : unit === "minute" ? "分" : unit === "hour" ? "時間" : "日";
|
|
353
315
|
return `${count}${suffix}`;
|
|
354
316
|
}
|
|
355
317
|
|
|
@@ -7,7 +7,7 @@ export function useCheckInvisible(className: string) {
|
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const handleScroll = () => {
|
|
10
|
-
const el = document.querySelector(`.${className}`);
|
|
10
|
+
const el = document.querySelector(`.${CSS.escape(className)}`);
|
|
11
11
|
if (el) {
|
|
12
12
|
const { top } = el.getBoundingClientRect();
|
|
13
13
|
setIsInvisible(top < 0);
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { formatClientDateTime } from "../datetime/dateTime.client";
|
|
5
|
-
import type {
|
|
6
|
-
DateInput,
|
|
7
|
-
DatePreset,
|
|
8
|
-
TimePreset,
|
|
9
|
-
} from "../datetime/dateTime.shared";
|
|
5
|
+
import type { DateInput, DatePreset, TimePreset } from "../datetime/dateTime.shared";
|
|
10
6
|
import { toDate } from "../datetime/dateTime.shared";
|
|
11
7
|
|
|
12
8
|
type UseClientDateTimeOptions = {
|
|
@@ -16,10 +12,7 @@ type UseClientDateTimeOptions = {
|
|
|
16
12
|
timePreset?: TimePreset;
|
|
17
13
|
};
|
|
18
14
|
|
|
19
|
-
export function useClientDateTime(
|
|
20
|
-
value: DateInput,
|
|
21
|
-
options?: UseClientDateTimeOptions,
|
|
22
|
-
) {
|
|
15
|
+
export function useClientDateTime(value: DateInput, options?: UseClientDateTimeOptions) {
|
|
23
16
|
const [ready, setReady] = useState(false);
|
|
24
17
|
const [text, setText] = useState("");
|
|
25
18
|
|
|
@@ -33,13 +26,7 @@ export function useClientDateTime(
|
|
|
33
26
|
}),
|
|
34
27
|
);
|
|
35
28
|
setReady(true);
|
|
36
|
-
}, [
|
|
37
|
-
options?.datePreset,
|
|
38
|
-
options?.locale,
|
|
39
|
-
options?.timePreset,
|
|
40
|
-
options?.timeZone,
|
|
41
|
-
value,
|
|
42
|
-
]);
|
|
29
|
+
}, [options?.datePreset, options?.locale, options?.timePreset, options?.timeZone, value]);
|
|
43
30
|
|
|
44
31
|
return {
|
|
45
32
|
ready,
|
|
@@ -7,9 +7,7 @@ import { usePreservedCallback } from "./usePreservedCallback";
|
|
|
7
7
|
export function debounce<F extends (...args: unknown[]) => void>(
|
|
8
8
|
func: F,
|
|
9
9
|
debounceMs: number,
|
|
10
|
-
{
|
|
11
|
-
edges = ["leading", "trailing"],
|
|
12
|
-
}: { edges?: Array<"leading" | "trailing"> } = {},
|
|
10
|
+
{ edges = ["leading", "trailing"] }: { edges?: Array<"leading" | "trailing"> } = {},
|
|
13
11
|
): {
|
|
14
12
|
(...args: Parameters<F>): void;
|
|
15
13
|
cancel: () => void;
|
|
@@ -51,10 +49,7 @@ export function debounce<F extends (...args: unknown[]) => void>(
|
|
|
51
49
|
}, debounceMs);
|
|
52
50
|
};
|
|
53
51
|
|
|
54
|
-
const debounced = function (
|
|
55
|
-
this: ThisParameterType<F>,
|
|
56
|
-
...args: Parameters<F>
|
|
57
|
-
) {
|
|
52
|
+
const debounced = function (this: ThisParameterType<F>, ...args: Parameters<F>) {
|
|
58
53
|
pendingThis = this;
|
|
59
54
|
pendingArgs = args;
|
|
60
55
|
|
|
@@ -88,11 +88,7 @@ export function useGeolocation(options?: UseGeolocationOptions) {
|
|
|
88
88
|
const getCurrentPosition = useCallback(() => {
|
|
89
89
|
if (!isSupported()) return;
|
|
90
90
|
setState((prev) => ({ ...prev, loading: true }));
|
|
91
|
-
navigator.geolocation.getCurrentPosition(
|
|
92
|
-
handleSuccess,
|
|
93
|
-
handleError,
|
|
94
|
-
geoOptions(),
|
|
95
|
-
);
|
|
91
|
+
navigator.geolocation.getCurrentPosition(handleSuccess, handleError, geoOptions());
|
|
96
92
|
}, [handleSuccess, handleError, geoOptions, isSupported]);
|
|
97
93
|
|
|
98
94
|
const startTracking = useCallback(() => {
|
|
@@ -15,8 +15,7 @@ type IntervalOptions =
|
|
|
15
15
|
export function useInterval(callback: () => void, options: IntervalOptions) {
|
|
16
16
|
const delay = typeof options === "number" ? options : options.delay;
|
|
17
17
|
const immediate = typeof options === "number" ? false : options.immediate;
|
|
18
|
-
const enabled =
|
|
19
|
-
typeof options === "number" ? true : (options.enabled ?? true);
|
|
18
|
+
const enabled = typeof options === "number" ? true : (options.enabled ?? true);
|
|
20
19
|
|
|
21
20
|
const preservedCallback = usePreservedCallback(callback);
|
|
22
21
|
const immediateCalledRef = useRef(false);
|
|
@@ -10,18 +10,13 @@ type UseKeyboardListNavigationOptions = {
|
|
|
10
10
|
itemCount: number;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export function useKeyboardListNavigation({
|
|
14
|
-
itemCount,
|
|
15
|
-
}: UseKeyboardListNavigationOptions) {
|
|
13
|
+
export function useKeyboardListNavigation({ itemCount }: UseKeyboardListNavigationOptions) {
|
|
16
14
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
17
15
|
const itemRefs = useRef<(HTMLElement | null)[]>([]);
|
|
18
16
|
|
|
19
|
-
const setItemRef = useCallback(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
[],
|
|
24
|
-
);
|
|
17
|
+
const setItemRef = useCallback((index: number, element: HTMLElement | null) => {
|
|
18
|
+
itemRefs.current[index] = element;
|
|
19
|
+
}, []);
|
|
25
20
|
|
|
26
21
|
const focusItem = useCallback((index: number) => {
|
|
27
22
|
const target = itemRefs.current[index];
|
|
@@ -59,18 +54,6 @@ export function useKeyboardListNavigation({
|
|
|
59
54
|
|
|
60
55
|
const moveActiveItem = useCallback(
|
|
61
56
|
(key: string) => {
|
|
62
|
-
if (itemCount === 0) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (key === "Home") {
|
|
67
|
-
return focusItem(0);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (key === "End") {
|
|
71
|
-
return focusItem(itemCount - 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
57
|
if (key === "ArrowDown" || key === "ArrowRight") {
|
|
75
58
|
const nextIndex = activeIndex < 0 ? 0 : (activeIndex + 1) % itemCount;
|
|
76
59
|
return focusItem(nextIndex);
|
|
@@ -78,15 +61,13 @@ export function useKeyboardListNavigation({
|
|
|
78
61
|
|
|
79
62
|
if (key === "ArrowUp" || key === "ArrowLeft") {
|
|
80
63
|
const nextIndex =
|
|
81
|
-
activeIndex < 0
|
|
82
|
-
? itemCount - 1
|
|
83
|
-
: (activeIndex - 1 + itemCount) % itemCount;
|
|
64
|
+
activeIndex < 0 ? itemCount - 1 : (activeIndex - 1 + itemCount) % itemCount;
|
|
84
65
|
return focusItem(nextIndex);
|
|
85
66
|
}
|
|
86
67
|
|
|
87
|
-
return
|
|
68
|
+
return focusBoundaryItem(key);
|
|
88
69
|
},
|
|
89
|
-
[activeIndex, focusItem, itemCount],
|
|
70
|
+
[activeIndex, focusItem, focusBoundaryItem, itemCount],
|
|
90
71
|
);
|
|
91
72
|
|
|
92
73
|
const clickActiveItem = useCallback(() => {
|
|
@@ -99,10 +80,7 @@ export function useKeyboardListNavigation({
|
|
|
99
80
|
}, [activeIndex]);
|
|
100
81
|
|
|
101
82
|
const handleListKeyDown = useCallback(
|
|
102
|
-
(
|
|
103
|
-
event: React.KeyboardEvent<HTMLElement>,
|
|
104
|
-
options?: { container?: HTMLElement | null },
|
|
105
|
-
) => {
|
|
83
|
+
(event: React.KeyboardEvent<HTMLElement>, options?: { container?: HTMLElement | null }) => {
|
|
106
84
|
if (itemCount === 0 || event.defaultPrevented) {
|
|
107
85
|
return;
|
|
108
86
|
}
|
|
@@ -144,15 +122,9 @@ export function useKeyboardListNavigation({
|
|
|
144
122
|
return;
|
|
145
123
|
}
|
|
146
124
|
|
|
147
|
-
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (focusBoundaryItem(event.key)) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
125
|
+
moveActiveItem(event.key);
|
|
154
126
|
},
|
|
155
|
-
[clickActiveItem, moveActiveItem,
|
|
127
|
+
[clickActiveItem, moveActiveItem, itemCount],
|
|
156
128
|
);
|
|
157
129
|
|
|
158
130
|
useEffect(() => {
|
|
@@ -14,12 +14,7 @@ export type UseLongPressOptions<E extends HTMLElement> = {
|
|
|
14
14
|
|
|
15
15
|
export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
16
16
|
onLongPress: (event: MouseEvent<E> | TouchEvent<E>) => void,
|
|
17
|
-
{
|
|
18
|
-
delay = 500,
|
|
19
|
-
moveThreshold,
|
|
20
|
-
onClick,
|
|
21
|
-
onLongPressEnd,
|
|
22
|
-
}: UseLongPressOptions<E> = {},
|
|
17
|
+
{ delay = 500, moveThreshold, onClick, onLongPressEnd }: UseLongPressOptions<E> = {},
|
|
23
18
|
) {
|
|
24
19
|
const timeoutRef = useRef<number | null>(null);
|
|
25
20
|
const isLongPressActiveRef = useRef(false);
|
|
@@ -32,22 +27,18 @@ export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
|
32
27
|
savedOnClick.current = onClick;
|
|
33
28
|
savedOnLongPressEnd.current = onLongPressEnd;
|
|
34
29
|
|
|
35
|
-
const hasThreshold =
|
|
36
|
-
moveThreshold?.x !== undefined || moveThreshold?.y !== undefined;
|
|
30
|
+
const hasThreshold = moveThreshold?.x !== undefined || moveThreshold?.y !== undefined;
|
|
37
31
|
|
|
38
|
-
const getClientPosition = useCallback(
|
|
39
|
-
(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
[],
|
|
50
|
-
);
|
|
32
|
+
const getClientPosition = useCallback((event: MouseEvent<E> | TouchEvent<E>) => {
|
|
33
|
+
if ("touches" in event.nativeEvent) {
|
|
34
|
+
const touch = event.nativeEvent.touches[0];
|
|
35
|
+
return { x: touch.clientX, y: touch.clientY };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
x: event.nativeEvent.clientX,
|
|
39
|
+
y: event.nativeEvent.clientY,
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
51
42
|
|
|
52
43
|
const isMovedBeyondThreshold = useCallback(
|
|
53
44
|
(event: MouseEvent<E> | TouchEvent<E>) => {
|
|
@@ -113,8 +104,6 @@ export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
|
113
104
|
onMouseLeave: cancelLongPress,
|
|
114
105
|
onTouchStart: handlePressStart,
|
|
115
106
|
onTouchEnd: handlePressEnd,
|
|
116
|
-
...(hasThreshold
|
|
117
|
-
? { onTouchMove: handlePressMove, onMouseMove: handlePressMove }
|
|
118
|
-
: {}),
|
|
107
|
+
...(hasThreshold ? { onTouchMove: handlePressMove, onMouseMove: handlePressMove } : {}),
|
|
119
108
|
};
|
|
120
109
|
}
|
|
@@ -12,10 +12,7 @@ type UseRelativeDateTimeOptions = {
|
|
|
12
12
|
updateIntervalMs?: number;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export function useRelativeDateTime(
|
|
16
|
-
value: DateInput,
|
|
17
|
-
options?: UseRelativeDateTimeOptions,
|
|
18
|
-
) {
|
|
15
|
+
export function useRelativeDateTime(value: DateInput, options?: UseRelativeDateTimeOptions) {
|
|
19
16
|
const [ready, setReady] = useState(false);
|
|
20
17
|
const [text, setText] = useState("");
|
|
21
18
|
const [isRelative, setIsRelative] = useState(false);
|
|
@@ -6,8 +6,7 @@ export function useViewportHeight() {
|
|
|
6
6
|
const [height, setHeight] = useState(0);
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
|
-
const update = () =>
|
|
10
|
-
setHeight(window.visualViewport?.height ?? window.innerHeight);
|
|
9
|
+
const update = () => setHeight(window.visualViewport?.height ?? window.innerHeight);
|
|
11
10
|
|
|
12
11
|
update();
|
|
13
12
|
window.visualViewport?.addEventListener("resize", update);
|
|
@@ -6,10 +6,6 @@ export function useViewportMatch(mediaQuery: string): boolean {
|
|
|
6
6
|
const [isMatched, setIsMatched] = useState(false);
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
|
-
if (typeof window === "undefined") {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
const mediaQueryList = window.matchMedia(mediaQuery);
|
|
14
10
|
const syncMatchState = () => {
|
|
15
11
|
setIsMatched(mediaQueryList.matches);
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { createContext, type ReactNode, useContext
|
|
3
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
4
4
|
|
|
5
5
|
export function buildContext<T>(defaultValue: T) {
|
|
6
6
|
const Ctx = createContext<T>(defaultValue);
|
|
7
7
|
|
|
8
8
|
function Provider({ value, children }: { value: T; children: ReactNode }) {
|
|
9
|
-
|
|
10
|
-
return <Ctx.Provider value={stable}>{children}</Ctx.Provider>;
|
|
9
|
+
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
function useValue() {
|