@musecat/functionkit 1.0.0 → 1.0.2

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.
Files changed (157) hide show
  1. package/.agents/references/components/ScrolltoTop.md +36 -0
  2. package/.agents/references/components/SwitchCase.md +47 -0
  3. package/.agents/references/components/ViewportPortal.md +34 -0
  4. package/.agents/references/cookie/cookie.md +47 -0
  5. package/.agents/references/datetime/dateTime.client.md +42 -0
  6. package/.agents/references/datetime/dateTime.server.md +42 -0
  7. package/.agents/references/datetime/dateTime.shared.md +86 -0
  8. package/.agents/references/hooks/useAvoidKeyboard.md +29 -0
  9. package/.agents/references/hooks/useCheckInvisible.md +36 -0
  10. package/.agents/references/hooks/useCheckScroll.md +36 -0
  11. package/.agents/references/hooks/useClientDateTime.md +39 -0
  12. package/.agents/references/hooks/useDebounce.md +46 -0
  13. package/.agents/references/hooks/useDebouncedCallback.md +34 -0
  14. package/.agents/references/hooks/useDoubleClick.md +40 -0
  15. package/.agents/references/hooks/useGeolocation.md +37 -0
  16. package/.agents/references/hooks/useHasMounted.md +27 -0
  17. package/.agents/references/hooks/useIntersectionObserver.md +37 -0
  18. package/.agents/references/hooks/useInterval.md +40 -0
  19. package/.agents/references/hooks/useKeyboardHeight.md +33 -0
  20. package/.agents/references/hooks/useKeyboardListNavigation.md +58 -0
  21. package/.agents/references/hooks/useLongPress.md +48 -0
  22. package/.agents/references/hooks/usePreservedCallback.md +33 -0
  23. package/.agents/references/hooks/usePreservedReference.md +34 -0
  24. package/.agents/references/hooks/useRefEffect.md +35 -0
  25. package/.agents/references/hooks/useRelativeDateTime.md +35 -0
  26. package/.agents/references/hooks/useTimeout.md +42 -0
  27. package/.agents/references/hooks/useToggleState.md +39 -0
  28. package/.agents/references/hooks/useViewportHeight.md +26 -0
  29. package/.agents/references/hooks/useViewportMatch.md +32 -0
  30. package/.agents/references/utils/browserStorage.md +41 -0
  31. package/.agents/references/utils/buildContext.md +47 -0
  32. package/.agents/references/utils/clipboardShare.md +59 -0
  33. package/.agents/references/utils/floatingMotion.md +67 -0
  34. package/.agents/references/utils/getDeviceInfo.md +43 -0
  35. package/.agents/references/utils/isEditableKeyboardTarget.md +35 -0
  36. package/.agents/references/utils/mergeRefs.md +29 -0
  37. package/.agents/references/utils/seen.md +37 -0
  38. package/.agents/references/utils/subscribeKeyboardHeight.md +35 -0
  39. package/AGENTS.md +103 -396
  40. package/LICENSE +9 -0
  41. package/README.md +38 -0
  42. package/index.ts +97 -102
  43. package/package.json +27 -13
  44. package/{components → packages/components}/ScrolltoTop.tsx +3 -3
  45. package/{components → packages/components}/SwitchCase.tsx +7 -7
  46. package/packages/components/ViewportPortal.tsx +40 -0
  47. package/packages/cookie/cookie.shared.ts +138 -0
  48. package/packages/datetime/dateTime.client.ts +108 -0
  49. package/packages/datetime/dateTime.server.ts +108 -0
  50. package/packages/datetime/dateTime.shared.ts +358 -0
  51. package/packages/hooks/useAvoidKeyboard.ts +28 -0
  52. package/packages/hooks/useCheckInvisible.ts +25 -0
  53. package/packages/hooks/useCheckScroll.ts +19 -0
  54. package/packages/hooks/useClientDateTime.ts +49 -0
  55. package/packages/hooks/useDebounce.ts +118 -0
  56. package/packages/hooks/useDebouncedCallback.ts +69 -0
  57. package/packages/hooks/useDoubleClick.ts +53 -0
  58. package/packages/hooks/useGeolocation.ts +146 -0
  59. package/packages/hooks/useHasMounted.ts +13 -0
  60. package/packages/hooks/useIntersectionObserver.ts +34 -0
  61. package/packages/hooks/useInterval.ts +56 -0
  62. package/packages/hooks/useKeyboardHeight.ts +18 -0
  63. package/packages/hooks/useKeyboardListNavigation.ts +170 -0
  64. package/packages/hooks/useLongPress.ts +120 -0
  65. package/packages/hooks/usePreservedCallback.ts +21 -0
  66. package/packages/hooks/usePreservedReference.ts +21 -0
  67. package/packages/hooks/useRefEffect.ts +35 -0
  68. package/packages/hooks/useRelativeDateTime.ts +54 -0
  69. package/packages/hooks/useTimeout.ts +56 -0
  70. package/packages/hooks/useToggleState.ts +13 -0
  71. package/packages/hooks/useViewportHeight.ts +23 -0
  72. package/packages/hooks/useViewportMatch.ts +27 -0
  73. package/packages/utils/browserStorage.ts +59 -0
  74. package/packages/utils/buildContext.tsx +19 -0
  75. package/packages/utils/checkDevice.ts +74 -0
  76. package/packages/utils/clipboardShare.tsx +47 -0
  77. package/{utils → packages/utils}/clipboardShare.types.ts +7 -7
  78. package/packages/utils/floatingMotion.ts +99 -0
  79. package/packages/utils/keyboardTarget.ts +13 -0
  80. package/packages/utils/mergeRefs.ts +15 -0
  81. package/packages/utils/seen.ts +29 -0
  82. package/packages/utils/subscribeKeyboardHeight.ts +54 -0
  83. package/tests/components/ScrolltoTop.test.tsx +15 -0
  84. package/tests/components/SwitchCase.test.tsx +34 -0
  85. package/tests/components/ViewportPortal.test.tsx +42 -0
  86. package/tests/cookie/cookie.test.ts +156 -0
  87. package/tests/datetime/datetime.test.ts +250 -0
  88. package/tests/hooks/useAvoidKeyboard.test.ts +12 -0
  89. package/tests/hooks/useCheckInvisible.test.ts +28 -0
  90. package/tests/hooks/useCheckScroll.test.ts +23 -0
  91. package/tests/hooks/useClientDateTime.test.ts +14 -0
  92. package/tests/hooks/useDebounce.test.ts +71 -0
  93. package/tests/hooks/useDebouncedCallback.test.ts +34 -0
  94. package/tests/hooks/useDoubleClick.test.ts +31 -0
  95. package/tests/hooks/useGeolocation.test.ts +50 -0
  96. package/tests/hooks/useHasMounted.test.ts +10 -0
  97. package/tests/hooks/useIntersectionObserver.test.ts +13 -0
  98. package/tests/hooks/useInterval.test.ts +31 -0
  99. package/tests/hooks/useKeyboardHeight.test.ts +10 -0
  100. package/tests/hooks/useKeyboardListNavigation.test.ts +82 -0
  101. package/tests/hooks/useLongPress.test.ts +52 -0
  102. package/tests/hooks/usePreservedCallback.test.ts +35 -0
  103. package/tests/hooks/usePreservedReference.test.ts +35 -0
  104. package/tests/hooks/useRefEffect.test.ts +30 -0
  105. package/tests/hooks/useRelativeDateTime.test.ts +19 -0
  106. package/tests/hooks/useTimeout.test.ts +34 -0
  107. package/tests/hooks/useToggleState.test.ts +35 -0
  108. package/tests/hooks/useViewportHeight.test.ts +10 -0
  109. package/tests/hooks/useViewportMatch.test.ts +35 -0
  110. package/tests/setup.ts +9 -0
  111. package/tests/utils/browserStorage.test.ts +54 -0
  112. package/tests/utils/buildContext.test.tsx +27 -0
  113. package/tests/utils/checkDevice.test.ts +84 -0
  114. package/tests/utils/clipboardShare.test.ts +78 -0
  115. package/tests/utils/floatingMotion.test.ts +84 -0
  116. package/tests/utils/keyboardTarget.test.ts +44 -0
  117. package/tests/utils/mergeRefs.test.ts +18 -0
  118. package/tests/utils/seen.test.ts +31 -0
  119. package/tests/utils/subscribeKeyboardHeight.test.ts +50 -0
  120. package/tsconfig.json +16 -16
  121. package/vitest.config.ts +16 -0
  122. package/components/ViewportPortal.tsx +0 -40
  123. package/cookie/cookie.shared.ts +0 -142
  124. package/datetime/dateTime.client.ts +0 -108
  125. package/datetime/dateTime.server.ts +0 -108
  126. package/datetime/dateTime.shared.ts +0 -358
  127. package/hooks/useAvoidKeyboard.ts +0 -28
  128. package/hooks/useCheckInvisible.ts +0 -25
  129. package/hooks/useCheckScroll.ts +0 -19
  130. package/hooks/useClientDateTime.ts +0 -49
  131. package/hooks/useDebounce.ts +0 -118
  132. package/hooks/useDebouncedCallback.ts +0 -69
  133. package/hooks/useDoubleClick.ts +0 -53
  134. package/hooks/useGeolocation.ts +0 -146
  135. package/hooks/useHasMounted.ts +0 -13
  136. package/hooks/useIntersectionObserver.ts +0 -34
  137. package/hooks/useInterval.ts +0 -55
  138. package/hooks/useKeyboardHeight.ts +0 -18
  139. package/hooks/useKeyboardListNavigation.ts +0 -170
  140. package/hooks/useLongPress.ts +0 -120
  141. package/hooks/usePreservedCallback.ts +0 -21
  142. package/hooks/usePreservedReference.ts +0 -21
  143. package/hooks/useRefEffect.ts +0 -35
  144. package/hooks/useRelativeDateTime.ts +0 -54
  145. package/hooks/useTimeout.ts +0 -56
  146. package/hooks/useToggleState.ts +0 -13
  147. package/hooks/useViewportHeight.ts +0 -23
  148. package/hooks/useViewportMatch.ts +0 -33
  149. package/utils/browserStorage.ts +0 -59
  150. package/utils/buildContext.tsx +0 -19
  151. package/utils/checkDevice.ts +0 -74
  152. package/utils/clipboardShare.tsx +0 -47
  153. package/utils/floatingMotion.ts +0 -99
  154. package/utils/keyboardTarget.ts +0 -13
  155. package/utils/mergeRefs.ts +0 -15
  156. package/utils/seen.ts +0 -29
  157. package/utils/subscribeKeyboardHeight.ts +0 -54
package/index.ts CHANGED
@@ -1,111 +1,106 @@
1
- // 컴포넌트
2
1
  export {
3
- getViewportPortalRoot,
4
- ViewportPortal
5
- } from "./components/ViewportPortal";
6
- // 쿠키
2
+ getViewportPortalRoot,
3
+ ViewportPortal,
4
+ } from "./packages/components/ViewportPortal";
7
5
  export {
8
- clearAllClientCookies,
9
- clearClientCookie,
10
- getClientCookie,
11
- parseClientCookieNames,
12
- setClientCookie
13
- } from "./cookie/cookie.shared";
14
- // 날짜/시간
6
+ clearAllClientCookies,
7
+ clearClientCookie,
8
+ getClientCookie,
9
+ parseClientCookieNames,
10
+ setClientCookie,
11
+ } from "./packages/cookie/cookie.shared";
15
12
  export {
16
- formatClientDate,
17
- formatClientDateTime,
18
- formatClientRelative,
19
- formatClientTime
20
- } from "./datetime/dateTime.client";
13
+ formatClientDate,
14
+ formatClientDateTime,
15
+ formatClientRelative,
16
+ formatClientTime,
17
+ } from "./packages/datetime/dateTime.client";
21
18
  export {
22
- formatServerDate,
23
- formatServerDateTime,
24
- formatServerRelative,
25
- formatServerTime
26
- } from "./datetime/dateTime.server";
27
- export {
28
- addUtcDays,
29
- format24HourTime,
30
- formatDotDate,
31
- formatKoreanTime,
32
- formatLongDate,
33
- formatRelativeText,
34
- formatRemainingText,
35
- formatTwelveHourTime,
36
- formatUtcDateKey,
37
- getUtcWeekdayIndex,
38
- normalizeAppLocale,
39
- parseUtcDateInput,
40
- toDate,
41
- toIntlLocale,
42
- toUtcMidnight
43
- } from "./datetime/dateTime.shared";
19
+ formatServerDate,
20
+ formatServerDateTime,
21
+ formatServerRelative,
22
+ formatServerTime,
23
+ } from "./packages/datetime/dateTime.server";
44
24
  export type {
45
- AppLocale,
46
- DateInput,
47
- DatePreset,
48
- TimePreset
49
- } from "./datetime/dateTime.shared";
50
- // hooks
51
- export { useAvoidKeyboard } from "./hooks/useAvoidKeyboard";
52
- export { useClientDateTime } from "./hooks/useClientDateTime";
53
- export { useDebounce } from "./hooks/useDebounce";
54
- export { useDebouncedCallback } from "./hooks/useDebouncedCallback";
55
- export { useDoubleClick } from "./hooks/useDoubleClick";
56
- export { useGeolocation } from "./hooks/useGeolocation";
57
- export { useHasMounted } from "./hooks/useHasMounted";
58
- export { useIntersectionObserver } from "./hooks/useIntersectionObserver";
59
- export { useInterval } from "./hooks/useInterval";
60
- export { useKeyboardHeight } from "./hooks/useKeyboardHeight";
61
- export { useKeyboardListNavigation } from "./hooks/useKeyboardListNavigation";
62
- export { useLongPress } from "./hooks/useLongPress";
63
- export { usePreservedCallback } from "./hooks/usePreservedCallback";
64
- export { usePreservedReference } from "./hooks/usePreservedReference";
65
- export { useRefEffect } from "./hooks/useRefEffect";
66
- export { useRelativeDateTime } from "./hooks/useRelativeDateTime";
67
- export { useTimeout } from "./hooks/useTimeout";
68
- export { useToggleState } from "./hooks/useToggleState";
69
- export { useViewportHeight } from "./hooks/useViewportHeight";
70
- export { useViewportMatch } from "./hooks/useViewportMatch";
71
- // 유틸리티
25
+ AppLocale,
26
+ DateInput,
27
+ DatePreset,
28
+ TimePreset,
29
+ } from "./packages/datetime/dateTime.shared";
72
30
  export {
73
- getLocalStorage,
74
- getSessionStorage,
75
- removeLocalStorage,
76
- removeSessionStorage,
77
- updateLocalStorage,
78
- updateSessionStorage
79
- } from "./utils/browserStorage";
80
- // 유틸리티
81
- export { buildContext } from "./utils/buildContext";
82
- export { getDeviceInfo } from "./utils/checkDevice";
83
- export type { DeviceInfo } from "./utils/checkDevice";
84
- export { NavigatorClipboard, NavigatorShare } from "./utils/clipboardShare";
85
- export type {
86
- NavigatorClipboardProps,
87
- NavigatorClipboardResult,
88
- NavigatorShareProps,
89
- NavigatorShareResult
90
- } from "./utils/clipboardShare.types";
31
+ addUtcDays,
32
+ format24HourTime,
33
+ formatDotDate,
34
+ formatKoreanTime,
35
+ formatLongDate,
36
+ formatRelativeText,
37
+ formatRemainingText,
38
+ formatTwelveHourTime,
39
+ formatUtcDateKey,
40
+ getUtcWeekdayIndex,
41
+ normalizeAppLocale,
42
+ parseUtcDateInput,
43
+ toDate,
44
+ toIntlLocale,
45
+ toUtcMidnight,
46
+ } from "./packages/datetime/dateTime.shared";
47
+ export { useAvoidKeyboard } from "./packages/hooks/useAvoidKeyboard";
48
+ export { useClientDateTime } from "./packages/hooks/useClientDateTime";
49
+ export { useDebounce } from "./packages/hooks/useDebounce";
50
+ export { useDebouncedCallback } from "./packages/hooks/useDebouncedCallback";
51
+ export { useDoubleClick } from "./packages/hooks/useDoubleClick";
52
+ export { useGeolocation } from "./packages/hooks/useGeolocation";
53
+ export { useHasMounted } from "./packages/hooks/useHasMounted";
54
+ export { useIntersectionObserver } from "./packages/hooks/useIntersectionObserver";
55
+ export { useInterval } from "./packages/hooks/useInterval";
56
+ export { useKeyboardHeight } from "./packages/hooks/useKeyboardHeight";
57
+ export { useKeyboardListNavigation } from "./packages/hooks/useKeyboardListNavigation";
58
+ export { useLongPress } from "./packages/hooks/useLongPress";
59
+ export { usePreservedCallback } from "./packages/hooks/usePreservedCallback";
60
+ export { usePreservedReference } from "./packages/hooks/usePreservedReference";
61
+ export { useRefEffect } from "./packages/hooks/useRefEffect";
62
+ export { useRelativeDateTime } from "./packages/hooks/useRelativeDateTime";
63
+ export { useTimeout } from "./packages/hooks/useTimeout";
64
+ export { useToggleState } from "./packages/hooks/useToggleState";
65
+ export { useViewportHeight } from "./packages/hooks/useViewportHeight";
66
+ export { useViewportMatch } from "./packages/hooks/useViewportMatch";
67
+ export {
68
+ getLocalStorage,
69
+ getSessionStorage,
70
+ removeLocalStorage,
71
+ removeSessionStorage,
72
+ updateLocalStorage,
73
+ updateSessionStorage,
74
+ } from "./packages/utils/browserStorage";
75
+ export { buildContext } from "./packages/utils/buildContext";
76
+ export type { DeviceInfo } from "./packages/utils/checkDevice";
77
+ export { getDeviceInfo } from "./packages/utils/checkDevice";
91
78
  export {
92
- getFloatingHiddenTransform,
93
- getFloatingMotionPreset,
94
- getFloatingTransformOrigin
95
- } from "./utils/floatingMotion";
79
+ NavigatorClipboard,
80
+ NavigatorShare,
81
+ } from "./packages/utils/clipboardShare";
96
82
  export type {
97
- FloatingMotionMode,
98
- FloatingMotionPreset,
99
- FloatingPlacement
100
- } from "./utils/floatingMotion";
101
- // 유틸리티
102
- export { isEditableKeyboardTarget } from "./utils/keyboardTarget";
103
- export { mergeRefs } from "./utils/mergeRefs";
83
+ NavigatorClipboardProps,
84
+ NavigatorClipboardResult,
85
+ NavigatorShareProps,
86
+ NavigatorShareResult,
87
+ } from "./packages/utils/clipboardShare.types";
88
+ export type {
89
+ FloatingMotionMode,
90
+ FloatingMotionPreset,
91
+ FloatingPlacement,
92
+ } from "./packages/utils/floatingMotion";
93
+ export {
94
+ getFloatingHiddenTransform,
95
+ getFloatingMotionPreset,
96
+ getFloatingTransformOrigin,
97
+ } from "./packages/utils/floatingMotion";
98
+ export { isEditableKeyboardTarget } from "./packages/utils/keyboardTarget";
99
+ export { mergeRefs } from "./packages/utils/mergeRefs";
104
100
  export {
105
- buildSeenValue,
106
- hasSeenKey,
107
- parseSeen,
108
- SEEN_STORAGE_KEY
109
- } from "./utils/seen";
110
- export { subscribeKeyboardHeight } from "./utils/subscribeKeyboardHeight";
111
-
101
+ buildSeenValue,
102
+ hasSeenKey,
103
+ parseSeen,
104
+ SEEN_STORAGE_KEY,
105
+ } from "./packages/utils/seen";
106
+ export { subscribeKeyboardHeight } from "./packages/utils/subscribeKeyboardHeight";
package/package.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
- "name": "@musecat/functionkit",
3
- "version": "1.0.0",
4
- "type": "module",
5
- "main": "./index.ts",
6
- "types": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts",
9
- "./*": "./*"
10
- },
11
- "peerDependencies": {
12
- "react": "^19.0.0",
13
- "react-dom": "^19.0.0"
14
- }
2
+ "name": "@musecat/functionkit",
3
+ "version": "1.0.2",
4
+ "type": "module",
5
+ "main": "./index.ts",
6
+ "types": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./*": "./*"
10
+ },
11
+ "scripts": {
12
+ "lint": "biome check",
13
+ "format": "prettier --write . && biome format --write",
14
+ "test": "vitest run"
15
+ },
16
+ "peerDependencies": {
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@biomejs/biome": "^2.5.4",
22
+ "@testing-library/jest-dom": "^6.6.3",
23
+ "@testing-library/react": "^16.2.0",
24
+ "@testing-library/user-event": "^14.6.1",
25
+ "jsdom": "^26.0.0",
26
+ "prettier": "^3.9.5",
27
+ "vitest": "^4.1.10"
28
+ }
15
29
  }
@@ -3,7 +3,7 @@
3
3
  import { useEffect } from "react";
4
4
 
5
5
  export function ScrolltoTop() {
6
- useEffect(() => {
7
- window.scrollTo(0, 0);
8
- });
6
+ useEffect(() => {
7
+ window.scrollTo(0, 0);
8
+ });
9
9
  }
@@ -1,15 +1,15 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
3
  type SwitchCaseProps<T extends string | number> = {
4
- value: T;
5
- cases: Partial<Record<T, ReactNode>>;
6
- otherwise?: ReactNode;
4
+ value: T;
5
+ cases: Partial<Record<T, ReactNode>>;
6
+ otherwise?: ReactNode;
7
7
  };
8
8
 
9
9
  export function SwitchCase<T extends string | number>({
10
- value,
11
- cases,
12
- otherwise = null,
10
+ value,
11
+ cases,
12
+ otherwise = null,
13
13
  }: SwitchCaseProps<T>) {
14
- return (cases[value] ?? otherwise) as ReactNode;
14
+ return (cases[value] ?? otherwise) as ReactNode;
15
15
  }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+
6
+ const VIEWPORT_PORTAL_ROOT_ID = "viewport-portal-root";
7
+
8
+ export function getViewportPortalRoot() {
9
+ if (typeof document === "undefined") {
10
+ return null;
11
+ }
12
+
13
+ const existingRoot = document.getElementById(VIEWPORT_PORTAL_ROOT_ID);
14
+ if (existingRoot instanceof HTMLDivElement) {
15
+ return existingRoot;
16
+ }
17
+
18
+ const root = document.createElement("div");
19
+ root.id = VIEWPORT_PORTAL_ROOT_ID;
20
+ root.setAttribute("data-viewport-portal-root", "true");
21
+ root.style.cssText =
22
+ "position:fixed;inset:0;pointer-events:none;z-index:9999;";
23
+ document.body.appendChild(root);
24
+ return root;
25
+ }
26
+
27
+ type ViewportPortalProps = {
28
+ children: React.ReactNode;
29
+ };
30
+
31
+ export function ViewportPortal({ children }: ViewportPortalProps) {
32
+ const [root, setRoot] = useState<HTMLElement | null>(null);
33
+
34
+ useEffect(() => {
35
+ setRoot(getViewportPortalRoot());
36
+ }, []);
37
+
38
+ if (!root) return null;
39
+ return createPortal(children, root);
40
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ const EXPIRED_COOKIE_DATE = "Thu, 01 Jan 1970 00:00:00 GMT";
4
+
5
+ type CookieStoreLike = {
6
+ delete: (name: string) => Promise<void>;
7
+ };
8
+
9
+ type DocumentCookieRef = {
10
+ cookie: string;
11
+ };
12
+
13
+ type ClearClientCookieOptions = {
14
+ hostname?: string;
15
+ path?: string;
16
+ documentRef?: DocumentCookieRef;
17
+ cookieStore?: CookieStoreLike;
18
+ };
19
+
20
+ type ClearAllClientCookiesOptions = ClearClientCookieOptions & {
21
+ includeRoot?: boolean;
22
+ cookieString?: string;
23
+ };
24
+
25
+ function findClientCookie(
26
+ name: string,
27
+ documentRef: DocumentCookieRef,
28
+ ): string | undefined {
29
+ const parsed = parseClientCookie(documentRef.cookie);
30
+ return parsed[name];
31
+ }
32
+
33
+ function parseClientCookie(cookieString: string): Record<string, string> {
34
+ return cookieString
35
+ .split(";")
36
+ .map((pair) => pair.trim().split("=") as [string, string])
37
+ .reduce<Record<string, string>>((acc, [key, value]) => {
38
+ if (key) {
39
+ acc[key] = value;
40
+ }
41
+ return acc;
42
+ }, {});
43
+ }
44
+
45
+ export function parseClientCookieNames(cookieString: string): string[] {
46
+ return cookieString
47
+ .split(";")
48
+ .map((entry) => {
49
+ const eqPos = entry.indexOf("=");
50
+ return eqPos > -1 ? entry.slice(0, eqPos).trim() : entry.trim();
51
+ })
52
+ .filter(Boolean);
53
+ }
54
+
55
+ export function getClientCookie(name: string): string | undefined {
56
+ if (typeof document === "undefined") {
57
+ return undefined;
58
+ }
59
+
60
+ return findClientCookie(name, document);
61
+ }
62
+
63
+ export function setClientCookie(name: string, value: string, days?: number) {
64
+ if (typeof document === "undefined") {
65
+ return;
66
+ }
67
+
68
+ let expires = "";
69
+ if (days) {
70
+ const date = new Date();
71
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
72
+ expires = `; expires=${date.toUTCString()}`;
73
+ }
74
+
75
+ document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`;
76
+ }
77
+
78
+ export function clearClientCookie(
79
+ name: string,
80
+ options: ClearClientCookieOptions = {},
81
+ ): void {
82
+ const {
83
+ hostname = window.location.hostname,
84
+ path = "/",
85
+ documentRef = document,
86
+ } = options;
87
+
88
+ documentRef.cookie = `${name}=; expires=${EXPIRED_COOKIE_DATE}; path=${path}; domain=${hostname};`;
89
+ }
90
+
91
+ export function clearAllClientCookies(
92
+ options: ClearAllClientCookiesOptions = {},
93
+ ): string[] {
94
+ const {
95
+ documentRef = document,
96
+ cookieStore,
97
+ includeRoot = false,
98
+ cookieString,
99
+ } = options;
100
+
101
+ const entries = cookieString
102
+ ? parseClientCookieNames(cookieString)
103
+ : documentRef.cookie
104
+ .split(/;\s*/)
105
+ .map((entry) => {
106
+ const eqPos = entry.indexOf("=");
107
+ return eqPos > -1 ? entry.slice(0, eqPos).trim() : entry.trim();
108
+ })
109
+ .filter(Boolean);
110
+
111
+ if (cookieStore) {
112
+ for (const name of entries) {
113
+ cookieStore.delete(name);
114
+ }
115
+ return entries;
116
+ }
117
+
118
+ const hostnameParts = window.location.hostname.split(".");
119
+ const { pathname } = window.location;
120
+
121
+ const paths = [pathname];
122
+ if (includeRoot) {
123
+ paths.unshift("/");
124
+ }
125
+
126
+ for (const name of entries) {
127
+ if (!name) continue;
128
+
129
+ for (const path of paths) {
130
+ for (let i = 0; i < hostnameParts.length; i++) {
131
+ const domain = hostnameParts.slice(i).join(".");
132
+ documentRef.cookie = `${name}=; expires=${EXPIRED_COOKIE_DATE}; path=${path}; domain=${domain};`;
133
+ }
134
+ }
135
+ }
136
+
137
+ return entries;
138
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ type DateInput,
3
+ type DatePreset,
4
+ format24HourTime,
5
+ formatDotDate,
6
+ formatKoreanTime,
7
+ formatLongDate,
8
+ formatRelativeText,
9
+ formatTwelveHourTime,
10
+ type TimePreset,
11
+ toDate,
12
+ } from "./dateTime.shared";
13
+
14
+ export function formatClientDate(
15
+ value: DateInput,
16
+ options: {
17
+ locale?: string;
18
+ timeZone?: string;
19
+ preset: DatePreset;
20
+ },
21
+ ): string {
22
+ const date = toDate(value);
23
+ if (!date) return "";
24
+
25
+ return options.preset === "dot"
26
+ ? formatDotDate(date, options.timeZone)
27
+ : formatLongDate(date, options.locale, options.timeZone);
28
+ }
29
+
30
+ export function formatClientTime(
31
+ value: DateInput,
32
+ options: {
33
+ locale?: string;
34
+ timeZone?: string;
35
+ preset: TimePreset;
36
+ },
37
+ ): string {
38
+ const date = toDate(value);
39
+ if (!date) return "";
40
+
41
+ switch (options.preset) {
42
+ case "ko":
43
+ return formatKoreanTime(date, options.timeZone);
44
+ case "12h":
45
+ return formatTwelveHourTime(date, options.locale, options.timeZone);
46
+ case "24h-second":
47
+ return format24HourTime(date, {
48
+ includeSeconds: true,
49
+ timeZone: options.timeZone,
50
+ });
51
+ default:
52
+ return format24HourTime(date, { timeZone: options.timeZone });
53
+ }
54
+ }
55
+
56
+ export function formatClientDateTime(
57
+ value: DateInput,
58
+ options: {
59
+ locale?: string;
60
+ timeZone?: string;
61
+ datePreset?: DatePreset;
62
+ timePreset?: TimePreset;
63
+ },
64
+ ): string {
65
+ const dateText = formatClientDate(value, {
66
+ locale: options.locale,
67
+ timeZone: options.timeZone,
68
+ preset: options.datePreset ?? "long",
69
+ });
70
+ const timeText = formatClientTime(value, {
71
+ locale: options.locale,
72
+ timeZone: options.timeZone,
73
+ preset: options.timePreset ?? "24h-minute",
74
+ });
75
+ return dateText && timeText
76
+ ? `${dateText} ${timeText}`
77
+ : dateText || timeText;
78
+ }
79
+
80
+ export function formatClientRelative(
81
+ value: DateInput,
82
+ options: {
83
+ locale?: string;
84
+ now?: DateInput;
85
+ maxRelativeDays?: number;
86
+ fallbackDatePreset?: DatePreset;
87
+ },
88
+ ): { text: string; isRelative: boolean } {
89
+ const date = toDate(value);
90
+ const now = toDate(options.now ?? Date.now());
91
+ if (!date || !now) {
92
+ return { text: "", isRelative: false };
93
+ }
94
+
95
+ const diffMs = Math.max(0, now.getTime() - date.getTime());
96
+ const maxRelativeMs = (options.maxRelativeDays ?? 7) * 24 * 60 * 60 * 1000;
97
+ if (diffMs > maxRelativeMs) {
98
+ return {
99
+ text: formatClientDate(date, {
100
+ locale: options.locale,
101
+ preset: options.fallbackDatePreset ?? "dot",
102
+ }),
103
+ isRelative: false,
104
+ };
105
+ }
106
+
107
+ return formatRelativeText(diffMs, options.locale);
108
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ type DateInput,
3
+ type DatePreset,
4
+ format24HourTime,
5
+ formatDotDate,
6
+ formatKoreanTime,
7
+ formatLongDate,
8
+ formatRelativeText,
9
+ formatTwelveHourTime,
10
+ type TimePreset,
11
+ toDate,
12
+ } from "./dateTime.shared";
13
+
14
+ export function formatServerDate(
15
+ value: DateInput,
16
+ options: {
17
+ locale?: string;
18
+ timeZone?: string;
19
+ preset: DatePreset;
20
+ },
21
+ ): string {
22
+ const date = toDate(value);
23
+ if (!date) return "";
24
+
25
+ return options.preset === "dot"
26
+ ? formatDotDate(date, options.timeZone)
27
+ : formatLongDate(date, options.locale, options.timeZone);
28
+ }
29
+
30
+ export function formatServerTime(
31
+ value: DateInput,
32
+ options: {
33
+ locale?: string;
34
+ timeZone?: string;
35
+ preset: TimePreset;
36
+ },
37
+ ): string {
38
+ const date = toDate(value);
39
+ if (!date) return "";
40
+
41
+ switch (options.preset) {
42
+ case "ko":
43
+ return formatKoreanTime(date, options.timeZone);
44
+ case "12h":
45
+ return formatTwelveHourTime(date, options.locale, options.timeZone);
46
+ case "24h-second":
47
+ return format24HourTime(date, {
48
+ includeSeconds: true,
49
+ timeZone: options.timeZone,
50
+ });
51
+ default:
52
+ return format24HourTime(date, { timeZone: options.timeZone });
53
+ }
54
+ }
55
+
56
+ export function formatServerDateTime(
57
+ value: DateInput,
58
+ options: {
59
+ locale?: string;
60
+ timeZone?: string;
61
+ datePreset?: DatePreset;
62
+ timePreset?: TimePreset;
63
+ },
64
+ ): string {
65
+ const dateText = formatServerDate(value, {
66
+ locale: options.locale,
67
+ timeZone: options.timeZone,
68
+ preset: options.datePreset ?? "long",
69
+ });
70
+ const timeText = formatServerTime(value, {
71
+ locale: options.locale,
72
+ timeZone: options.timeZone,
73
+ preset: options.timePreset ?? "24h-minute",
74
+ });
75
+ return dateText && timeText
76
+ ? `${dateText} ${timeText}`
77
+ : dateText || timeText;
78
+ }
79
+
80
+ export function formatServerRelative(
81
+ value: DateInput,
82
+ options: {
83
+ locale?: string;
84
+ now?: DateInput;
85
+ maxRelativeDays?: number;
86
+ fallbackDatePreset?: DatePreset;
87
+ },
88
+ ): { text: string; isRelative: boolean } {
89
+ const date = toDate(value);
90
+ const now = toDate(options.now ?? new Date());
91
+ if (!date || !now) {
92
+ return { text: "", isRelative: false };
93
+ }
94
+
95
+ const diffMs = Math.max(0, now.getTime() - date.getTime());
96
+ const maxRelativeMs = (options.maxRelativeDays ?? 7) * 24 * 60 * 60 * 1000;
97
+ if (diffMs > maxRelativeMs) {
98
+ return {
99
+ text: formatServerDate(date, {
100
+ locale: options.locale,
101
+ preset: options.fallbackDatePreset ?? "dot",
102
+ }),
103
+ isRelative: false,
104
+ };
105
+ }
106
+
107
+ return formatRelativeText(diffMs, options.locale);
108
+ }