@musecat/functionkit 1.0.1 → 1.1.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.
Files changed (130) 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 +41 -0
  18. package/.agents/references/hooks/useInterval.md +44 -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 +37 -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/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  40. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  41. package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  42. package/.github/workflows/ci.yml +20 -0
  43. package/.local/state/gh/device-id +1 -0
  44. package/.prettierrc +8 -0
  45. package/AGENTS.md +117 -396
  46. package/CODE_OF_CONDUCT.md +55 -0
  47. package/CONTRIBUTING.md +39 -0
  48. package/README.md +18 -5
  49. package/biome.json +25 -0
  50. package/index.ts +63 -68
  51. package/package.json +46 -13
  52. package/packages/components/ScrolltoTop.tsx +3 -3
  53. package/packages/components/SwitchCase.tsx +7 -7
  54. package/packages/components/ViewportPortal.tsx +22 -23
  55. package/packages/cookie/cookie.shared.ts +80 -110
  56. package/packages/cookie/cookieNames.shared.ts +9 -0
  57. package/packages/datetime/dateTime.client.ts +84 -86
  58. package/packages/datetime/dateTime.server.ts +84 -86
  59. package/packages/datetime/dateTime.shared.ts +249 -287
  60. package/packages/hooks/useAvoidKeyboard.ts +15 -15
  61. package/packages/hooks/useCheckInvisible.ts +16 -16
  62. package/packages/hooks/useCheckScroll.ts +10 -10
  63. package/packages/hooks/useClientDateTime.ts +24 -37
  64. package/packages/hooks/useDebounce.ts +90 -95
  65. package/packages/hooks/useDebouncedCallback.ts +45 -45
  66. package/packages/hooks/useDoubleClick.ts +43 -43
  67. package/packages/hooks/useGeolocation.ts +126 -130
  68. package/packages/hooks/useHasMounted.ts +5 -5
  69. package/packages/hooks/useIntersectionObserver.ts +20 -20
  70. package/packages/hooks/useInterval.ts +45 -45
  71. package/packages/hooks/useKeyboardHeight.ts +9 -9
  72. package/packages/hooks/useKeyboardListNavigation.ts +130 -158
  73. package/packages/hooks/useLongPress.ts +87 -98
  74. package/packages/hooks/usePreservedCallback.ts +12 -12
  75. package/packages/hooks/usePreservedReference.ts +10 -10
  76. package/packages/hooks/useRefEffect.ts +19 -19
  77. package/packages/hooks/useRelativeDateTime.ts +36 -39
  78. package/packages/hooks/useTimeout.ts +41 -41
  79. package/packages/hooks/useToggleState.ts +5 -5
  80. package/packages/hooks/useViewportHeight.ts +12 -13
  81. package/packages/hooks/useViewportMatch.ts +13 -23
  82. package/packages/utils/browserStorage.ts +26 -26
  83. package/packages/utils/buildContext.tsx +10 -11
  84. package/packages/utils/checkDevice.ts +64 -64
  85. package/packages/utils/clipboardShare.tsx +31 -31
  86. package/packages/utils/clipboardShare.types.ts +7 -7
  87. package/packages/utils/floatingMotion.ts +71 -77
  88. package/packages/utils/keyboardTarget.ts +9 -9
  89. package/packages/utils/mergeRefs.ts +9 -9
  90. package/packages/utils/seen.ts +15 -15
  91. package/packages/utils/subscribeKeyboardHeight.ts +49 -46
  92. package/tests/components/ScrolltoTop.test.tsx +15 -0
  93. package/tests/components/SwitchCase.test.tsx +33 -0
  94. package/tests/components/ViewportPortal.test.tsx +46 -0
  95. package/tests/cookie/cookie.test.ts +192 -0
  96. package/tests/datetime/datetime.test.ts +461 -0
  97. package/tests/hooks/useAvoidKeyboard.test.ts +43 -0
  98. package/tests/hooks/useCheckInvisible.test.ts +59 -0
  99. package/tests/hooks/useCheckScroll.test.ts +23 -0
  100. package/tests/hooks/useClientDateTime.test.ts +12 -0
  101. package/tests/hooks/useDebounce.test.ts +96 -0
  102. package/tests/hooks/useDebouncedCallback.test.ts +63 -0
  103. package/tests/hooks/useDoubleClick.test.ts +82 -0
  104. package/tests/hooks/useGeolocation.test.ts +201 -0
  105. package/tests/hooks/useHasMounted.test.ts +10 -0
  106. package/tests/hooks/useIntersectionObserver.test.ts +78 -0
  107. package/tests/hooks/useInterval.test.ts +56 -0
  108. package/tests/hooks/useKeyboardHeight.test.ts +10 -0
  109. package/tests/hooks/useKeyboardListNavigation.test.ts +404 -0
  110. package/tests/hooks/useLongPress.test.ts +214 -0
  111. package/tests/hooks/usePreservedCallback.test.ts +33 -0
  112. package/tests/hooks/usePreservedReference.test.ts +42 -0
  113. package/tests/hooks/useRefEffect.test.ts +67 -0
  114. package/tests/hooks/useRelativeDateTime.test.ts +21 -0
  115. package/tests/hooks/useTimeout.test.ts +72 -0
  116. package/tests/hooks/useToggleState.test.ts +43 -0
  117. package/tests/hooks/useViewportHeight.test.ts +32 -0
  118. package/tests/hooks/useViewportMatch.test.ts +35 -0
  119. package/tests/setup.ts +9 -0
  120. package/tests/utils/browserStorage.test.ts +114 -0
  121. package/tests/utils/buildContext.test.tsx +26 -0
  122. package/tests/utils/checkDevice.test.ts +140 -0
  123. package/tests/utils/clipboardShare.test.ts +152 -0
  124. package/tests/utils/floatingMotion.test.ts +135 -0
  125. package/tests/utils/keyboardTarget.test.ts +85 -0
  126. package/tests/utils/mergeRefs.test.ts +50 -0
  127. package/tests/utils/seen.test.ts +40 -0
  128. package/tests/utils/subscribeKeyboardHeight.test.ts +172 -0
  129. package/tsconfig.json +16 -16
  130. package/vitest.config.ts +22 -0
@@ -6,49 +6,46 @@ import type { DateInput, DatePreset } from "../datetime/dateTime.shared";
6
6
  import { toDate } from "../datetime/dateTime.shared";
7
7
 
8
8
  type UseRelativeDateTimeOptions = {
9
- locale?: string;
10
- maxRelativeDays?: number;
11
- fallbackDatePreset?: DatePreset;
12
- updateIntervalMs?: number;
9
+ locale?: string;
10
+ maxRelativeDays?: number;
11
+ fallbackDatePreset?: DatePreset;
12
+ updateIntervalMs?: number;
13
13
  };
14
14
 
15
- export function useRelativeDateTime(
16
- value: DateInput,
17
- options?: UseRelativeDateTimeOptions,
18
- ) {
19
- const [ready, setReady] = useState(false);
20
- const [text, setText] = useState("");
21
- const [isRelative, setIsRelative] = useState(false);
15
+ export function useRelativeDateTime(value: DateInput, options?: UseRelativeDateTimeOptions) {
16
+ const [ready, setReady] = useState(false);
17
+ const [text, setText] = useState("");
18
+ const [isRelative, setIsRelative] = useState(false);
22
19
 
23
- useEffect(() => {
24
- const update = () => {
25
- const next = formatClientRelative(value, {
26
- locale: options?.locale,
27
- maxRelativeDays: options?.maxRelativeDays,
28
- fallbackDatePreset: options?.fallbackDatePreset,
29
- now: Date.now(),
30
- });
31
- setText(next.text);
32
- setIsRelative(next.isRelative);
33
- setReady(true);
34
- };
20
+ useEffect(() => {
21
+ const update = () => {
22
+ const next = formatClientRelative(value, {
23
+ locale: options?.locale,
24
+ maxRelativeDays: options?.maxRelativeDays,
25
+ fallbackDatePreset: options?.fallbackDatePreset,
26
+ now: Date.now(),
27
+ });
28
+ setText(next.text);
29
+ setIsRelative(next.isRelative);
30
+ setReady(true);
31
+ };
35
32
 
36
- update();
33
+ update();
37
34
 
38
- const timer = window.setInterval(update, options?.updateIntervalMs ?? 1000);
39
- return () => window.clearInterval(timer);
40
- }, [
41
- options?.fallbackDatePreset,
42
- options?.locale,
43
- options?.maxRelativeDays,
44
- options?.updateIntervalMs,
45
- value,
46
- ]);
35
+ const timer = window.setInterval(update, options?.updateIntervalMs ?? 1000);
36
+ return () => window.clearInterval(timer);
37
+ }, [
38
+ options?.fallbackDatePreset,
39
+ options?.locale,
40
+ options?.maxRelativeDays,
41
+ options?.updateIntervalMs,
42
+ value,
43
+ ]);
47
44
 
48
- return {
49
- ready,
50
- text,
51
- isRelative,
52
- date: toDate(value),
53
- };
45
+ return {
46
+ ready,
47
+ text,
48
+ isRelative,
49
+ date: toDate(value),
50
+ };
54
51
  }
@@ -5,52 +5,52 @@ import { useCallback, useEffect, useRef } from "react";
5
5
  import { usePreservedCallback } from "./usePreservedCallback";
6
6
 
7
7
  type UseTimeoutOptions = {
8
- enabled?: boolean;
8
+ enabled?: boolean;
9
9
  };
10
10
 
11
11
  export type UseTimeoutControls = {
12
- start: (overrideDelay?: number) => void;
13
- reset: (overrideDelay?: number) => void;
14
- clear: () => void;
15
- isPending: () => boolean;
12
+ start: (overrideDelay?: number) => void;
13
+ reset: (overrideDelay?: number) => void;
14
+ clear: () => void;
15
+ isPending: () => boolean;
16
16
  };
17
17
 
18
18
  export function useTimeout(
19
- callback: () => void,
20
- delay = 0,
21
- options?: UseTimeoutOptions,
19
+ callback: () => void,
20
+ delay = 0,
21
+ options?: UseTimeoutOptions,
22
22
  ): UseTimeoutControls {
23
- const { enabled = true } = options ?? {};
24
- const preservedCallback = usePreservedCallback(callback);
25
- const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
26
-
27
- const clear = useCallback(() => {
28
- if (timeoutRef.current !== null) {
29
- clearTimeout(timeoutRef.current);
30
- timeoutRef.current = null;
31
- }
32
- }, []);
33
-
34
- const start = useCallback(
35
- (overrideDelay?: number) => {
36
- clear();
37
- timeoutRef.current = setTimeout(() => {
38
- timeoutRef.current = null;
39
- preservedCallback();
40
- }, overrideDelay ?? delay);
41
- },
42
- [clear, delay, preservedCallback],
43
- );
44
-
45
- const isPending = useCallback(() => timeoutRef.current !== null, []);
46
-
47
- useEffect(() => {
48
- if (!enabled) return;
49
- start();
50
- return clear;
51
- }, [enabled, start, clear]);
52
-
53
- useEffect(() => clear, [clear]);
54
-
55
- return { start, reset: start, clear, isPending };
23
+ const { enabled = true } = options ?? {};
24
+ const preservedCallback = usePreservedCallback(callback);
25
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
26
+
27
+ const clear = useCallback(() => {
28
+ if (timeoutRef.current !== null) {
29
+ clearTimeout(timeoutRef.current);
30
+ timeoutRef.current = null;
31
+ }
32
+ }, []);
33
+
34
+ const start = useCallback(
35
+ (overrideDelay?: number) => {
36
+ clear();
37
+ timeoutRef.current = setTimeout(() => {
38
+ timeoutRef.current = null;
39
+ preservedCallback();
40
+ }, overrideDelay ?? delay);
41
+ },
42
+ [clear, delay, preservedCallback],
43
+ );
44
+
45
+ const isPending = useCallback(() => timeoutRef.current !== null, []);
46
+
47
+ useEffect(() => {
48
+ if (!enabled) return;
49
+ start();
50
+ return clear;
51
+ }, [enabled, start, clear]);
52
+
53
+ useEffect(() => clear, [clear]);
54
+
55
+ return { start, reset: start, clear, isPending };
56
56
  }
@@ -3,11 +3,11 @@
3
3
  import { useCallback, useState } from "react";
4
4
 
5
5
  export function useToggleState(initialValue = false) {
6
- const [value, setValue] = useState(initialValue);
6
+ const [value, setValue] = useState(initialValue);
7
7
 
8
- const setTrue = useCallback(() => setValue(true), []);
9
- const setFalse = useCallback(() => setValue(false), []);
10
- const toggle = useCallback(() => setValue((v) => !v), []);
8
+ const setTrue = useCallback(() => setValue(true), []);
9
+ const setFalse = useCallback(() => setValue(false), []);
10
+ const toggle = useCallback(() => setValue((v) => !v), []);
11
11
 
12
- return { value, setValue, setTrue, setFalse, toggle };
12
+ return { value, setValue, setTrue, setFalse, toggle };
13
13
  }
@@ -3,21 +3,20 @@
3
3
  import { useEffect, useState } from "react";
4
4
 
5
5
  export function useViewportHeight() {
6
- const [height, setHeight] = useState(0);
6
+ const [height, setHeight] = useState(0);
7
7
 
8
- useEffect(() => {
9
- const update = () =>
10
- setHeight(window.visualViewport?.height ?? window.innerHeight);
8
+ useEffect(() => {
9
+ const update = () => setHeight(window.visualViewport?.height ?? window.innerHeight);
11
10
 
12
- update();
13
- window.visualViewport?.addEventListener("resize", update);
14
- window.addEventListener("resize", update);
11
+ update();
12
+ window.visualViewport?.addEventListener("resize", update);
13
+ window.addEventListener("resize", update);
15
14
 
16
- return () => {
17
- window.visualViewport?.removeEventListener("resize", update);
18
- window.removeEventListener("resize", update);
19
- };
20
- }, []);
15
+ return () => {
16
+ window.visualViewport?.removeEventListener("resize", update);
17
+ window.removeEventListener("resize", update);
18
+ };
19
+ }, []);
21
20
 
22
- return { height };
21
+ return { height };
23
22
  }
@@ -2,32 +2,22 @@
2
2
 
3
3
  import { useEffect, useState } from "react";
4
4
 
5
- /**
6
- * Tracks whether the current viewport matches a media query.
7
- *
8
- * @param mediaQuery CSS media query string.
9
- * @returns True when current viewport matches the given query.
10
- */
11
5
  export function useViewportMatch(mediaQuery: string): boolean {
12
- const [isMatched, setIsMatched] = useState(false);
6
+ const [isMatched, setIsMatched] = useState(false);
13
7
 
14
- useEffect(() => {
15
- if (typeof window === "undefined") {
16
- return;
17
- }
8
+ useEffect(() => {
9
+ const mediaQueryList = window.matchMedia(mediaQuery);
10
+ const syncMatchState = () => {
11
+ setIsMatched(mediaQueryList.matches);
12
+ };
18
13
 
19
- const mediaQueryList = window.matchMedia(mediaQuery);
20
- const syncMatchState = () => {
21
- setIsMatched(mediaQueryList.matches);
22
- };
14
+ syncMatchState();
15
+ mediaQueryList.addEventListener("change", syncMatchState);
23
16
 
24
- syncMatchState();
25
- mediaQueryList.addEventListener("change", syncMatchState);
17
+ return () => {
18
+ mediaQueryList.removeEventListener("change", syncMatchState);
19
+ };
20
+ }, [mediaQuery]);
26
21
 
27
- return () => {
28
- mediaQueryList.removeEventListener("change", syncMatchState);
29
- };
30
- }, [mediaQuery]);
31
-
32
- return isMatched;
22
+ return isMatched;
33
23
  }
@@ -3,57 +3,57 @@
3
3
  type StorageKind = "localStorage" | "sessionStorage";
4
4
 
5
5
  function getStorage(kind: StorageKind): Storage | null {
6
- if (typeof window === "undefined") return null;
7
- return window[kind];
6
+ if (typeof window === "undefined") return null;
7
+ return window[kind];
8
8
  }
9
9
 
10
10
  function saveStorage(kind: StorageKind, key: string, value: unknown) {
11
- const storage = getStorage(kind);
12
- if (!storage) return;
13
- try {
14
- storage.setItem(key, JSON.stringify(value));
15
- } catch {}
11
+ const storage = getStorage(kind);
12
+ if (!storage) return;
13
+ try {
14
+ storage.setItem(key, JSON.stringify(value));
15
+ } catch {}
16
16
  }
17
17
 
18
18
  function loadStorage(kind: StorageKind, key: string): unknown {
19
- const storage = getStorage(kind);
20
- if (!storage) return null;
21
- try {
22
- const raw = storage.getItem(key);
23
- return raw === null ? null : (JSON.parse(raw) as unknown);
24
- } catch {
25
- return null;
26
- }
19
+ const storage = getStorage(kind);
20
+ if (!storage) return null;
21
+ try {
22
+ const raw = storage.getItem(key);
23
+ return raw === null ? null : (JSON.parse(raw) as unknown);
24
+ } catch {
25
+ return null;
26
+ }
27
27
  }
28
28
 
29
29
  function removeStorage(kind: StorageKind, key: string) {
30
- const storage = getStorage(kind);
31
- if (!storage) return;
32
- try {
33
- storage.removeItem(key);
34
- } catch {}
30
+ const storage = getStorage(kind);
31
+ if (!storage) return;
32
+ try {
33
+ storage.removeItem(key);
34
+ } catch {}
35
35
  }
36
36
 
37
37
  export function getLocalStorage(key: string): unknown {
38
- return loadStorage("localStorage", key);
38
+ return loadStorage("localStorage", key);
39
39
  }
40
40
 
41
41
  export function updateLocalStorage(key: string, value: unknown) {
42
- saveStorage("localStorage", key, value);
42
+ saveStorage("localStorage", key, value);
43
43
  }
44
44
 
45
45
  export function removeLocalStorage(key: string) {
46
- removeStorage("localStorage", key);
46
+ removeStorage("localStorage", key);
47
47
  }
48
48
 
49
49
  export function getSessionStorage(key: string): unknown {
50
- return loadStorage("sessionStorage", key);
50
+ return loadStorage("sessionStorage", key);
51
51
  }
52
52
 
53
53
  export function updateSessionStorage(key: string, value: unknown) {
54
- saveStorage("sessionStorage", key, value);
54
+ saveStorage("sessionStorage", key, value);
55
55
  }
56
56
 
57
57
  export function removeSessionStorage(key: string) {
58
- removeStorage("sessionStorage", key);
58
+ removeStorage("sessionStorage", key);
59
59
  }
@@ -1,19 +1,18 @@
1
1
  "use client";
2
2
 
3
- import { createContext, type ReactNode, useContext, useMemo } from "react";
3
+ import { createContext, type ReactNode, useContext } from "react";
4
4
 
5
5
  export function buildContext<T>(defaultValue: T) {
6
- const Ctx = createContext<T>(defaultValue);
6
+ const Ctx = createContext<T>(defaultValue);
7
7
 
8
- function Provider({ value, children }: { value: T; children: ReactNode }) {
9
- const stable = useMemo(() => value, [value]);
10
- return <Ctx.Provider value={stable}>{children}</Ctx.Provider>;
11
- }
8
+ function Provider({ value, children }: { value: T; children: ReactNode }) {
9
+ return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
10
+ }
12
11
 
13
- function useValue() {
14
- const ctx = useContext(Ctx);
15
- return ctx;
16
- }
12
+ function useValue() {
13
+ const ctx = useContext(Ctx);
14
+ return ctx;
15
+ }
17
16
 
18
- return [Provider, useValue] as const;
17
+ return [Provider, useValue] as const;
19
18
  }
@@ -1,74 +1,74 @@
1
1
  export type DeviceInfo = {
2
- isMobile: boolean;
3
- isIOS: boolean;
4
- isAndroid: boolean;
5
- isSafari: boolean;
6
- isIOSSafari: boolean;
7
- isMacSafari: boolean;
8
- isSamsungBrowser: boolean;
9
- isTouchDevice: boolean;
10
- browser: string;
2
+ isMobile: boolean;
3
+ isIOS: boolean;
4
+ isAndroid: boolean;
5
+ isSafari: boolean;
6
+ isIOSSafari: boolean;
7
+ isMacSafari: boolean;
8
+ isSamsungBrowser: boolean;
9
+ isTouchDevice: boolean;
10
+ browser: string;
11
11
  };
12
12
 
13
13
  export function getDeviceInfo(): DeviceInfo {
14
- let isMobile = false;
15
- let isIOS = false;
16
- let isAndroid = false;
17
- let isSafari = false;
18
- let isIOSSafari = false;
19
- let isMacSafari = false;
20
- let isSamsungBrowser = false;
21
- let isTouchDevice = false;
22
- let browser = "unknown";
14
+ let isMobile = false;
15
+ let isIOS = false;
16
+ let isAndroid = false;
17
+ let isSafari = false;
18
+ let isIOSSafari = false;
19
+ let isMacSafari = false;
20
+ let isSamsungBrowser = false;
21
+ let isTouchDevice = false;
22
+ let browser = "unknown";
23
23
 
24
- if (typeof window === "undefined") {
25
- return {
26
- isMobile,
27
- isIOS,
28
- isAndroid,
29
- isSafari,
30
- isIOSSafari,
31
- isMacSafari,
32
- isSamsungBrowser,
33
- isTouchDevice,
34
- browser,
35
- };
36
- }
24
+ if (typeof window === "undefined") {
25
+ return {
26
+ isMobile,
27
+ isIOS,
28
+ isAndroid,
29
+ isSafari,
30
+ isIOSSafari,
31
+ isMacSafari,
32
+ isSamsungBrowser,
33
+ isTouchDevice,
34
+ browser,
35
+ };
36
+ }
37
37
 
38
- const ua = navigator.userAgent;
38
+ const ua = navigator.userAgent;
39
39
 
40
- isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(ua);
41
- isIOS = /iPhone|iPad|iPod/i.test(ua);
42
- isAndroid = /Android/i.test(ua);
43
- isSafari = /Safari/i.test(ua) && !/Chrome/i.test(ua);
44
- isIOSSafari = isIOS && isSafari;
45
- isMacSafari = /Macintosh/i.test(ua) && isSafari && !isIOS;
46
- isSamsungBrowser = /SamsungBrowser/i.test(ua);
47
- isTouchDevice = "ontouchstart" in window;
40
+ isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(ua);
41
+ isIOS = /iPhone|iPad|iPod/i.test(ua);
42
+ isAndroid = /Android/i.test(ua);
43
+ isSafari = /Safari/i.test(ua) && !/Chrome/i.test(ua);
44
+ isIOSSafari = isIOS && isSafari;
45
+ isMacSafari = /Macintosh/i.test(ua) && isSafari && !isIOS;
46
+ isSamsungBrowser = /SamsungBrowser/i.test(ua);
47
+ isTouchDevice = "ontouchstart" in window;
48
48
 
49
- if (isSamsungBrowser) {
50
- browser = "samsung";
51
- } else if (isSafari) {
52
- browser = "safari";
53
- } else if (/Chrome/i.test(ua)) {
54
- browser = "chrome";
55
- } else if (/Firefox/i.test(ua)) {
56
- browser = "firefox";
57
- } else if (/Edge/i.test(ua)) {
58
- browser = "edge";
59
- } else {
60
- browser = "unknown";
61
- }
49
+ if (isSamsungBrowser) {
50
+ browser = "samsung";
51
+ } else if (isSafari) {
52
+ browser = "safari";
53
+ } else if (/Chrome/i.test(ua)) {
54
+ browser = "chrome";
55
+ } else if (/Firefox/i.test(ua)) {
56
+ browser = "firefox";
57
+ } else if (/Edge/i.test(ua)) {
58
+ browser = "edge";
59
+ } else {
60
+ browser = "unknown";
61
+ }
62
62
 
63
- return {
64
- isMobile,
65
- isIOS,
66
- isAndroid,
67
- isSafari,
68
- isIOSSafari,
69
- isMacSafari,
70
- isSamsungBrowser,
71
- isTouchDevice,
72
- browser,
73
- };
63
+ return {
64
+ isMobile,
65
+ isIOS,
66
+ isAndroid,
67
+ isSafari,
68
+ isIOSSafari,
69
+ isMacSafari,
70
+ isSamsungBrowser,
71
+ isTouchDevice,
72
+ browser,
73
+ };
74
74
  }
@@ -1,47 +1,47 @@
1
1
  "use client";
2
2
 
3
3
  import type {
4
- NavigatorClipboardProps,
5
- NavigatorClipboardResult,
6
- NavigatorShareProps,
7
- NavigatorShareResult,
4
+ NavigatorClipboardProps,
5
+ NavigatorClipboardResult,
6
+ NavigatorShareProps,
7
+ NavigatorShareResult,
8
8
  } from "./clipboardShare.types";
9
9
 
10
10
  export function NavigatorClipboard({
11
- text,
11
+ text,
12
12
  }: NavigatorClipboardProps): Promise<NavigatorClipboardResult> {
13
- return navigator.clipboard.writeText(text).then(
14
- () => ({ success: true }),
15
- () => ({ success: false }),
16
- );
13
+ return navigator.clipboard.writeText(text).then(
14
+ () => ({ success: true }),
15
+ () => ({ success: false }),
16
+ );
17
17
  }
18
18
 
19
19
  export function NavigatorShare({
20
- title,
21
- text,
22
- link,
20
+ title,
21
+ text,
22
+ link,
23
23
  }: NavigatorShareProps): Promise<NavigatorShareResult> {
24
- if (navigator.share) {
25
- return navigator.share({ title, text, url: link }).then(
26
- () => ({ success: true, method: "share" }),
27
- (error) => {
28
- if (error instanceof DOMException && error.name === "AbortError") {
29
- return { success: false, method: "share" };
30
- }
31
- return clipboardFallback(link);
32
- },
33
- );
34
- }
24
+ if (navigator.share) {
25
+ return navigator.share({ title, text, url: link }).then(
26
+ () => ({ success: true, method: "share" }),
27
+ (error) => {
28
+ if (error instanceof DOMException && error.name === "AbortError") {
29
+ return { success: false, method: "share" };
30
+ }
31
+ return clipboardFallback(link);
32
+ },
33
+ );
34
+ }
35
35
 
36
- return clipboardFallback(link);
36
+ return clipboardFallback(link);
37
37
  }
38
38
 
39
39
  function clipboardFallback(link: string): Promise<NavigatorShareResult> {
40
- if (navigator.clipboard) {
41
- return navigator.clipboard.writeText(link).then(
42
- () => ({ success: true, method: "clipboard" }),
43
- () => ({ success: false, method: "unsupported" }),
44
- );
45
- }
46
- return Promise.resolve({ success: false, method: "unsupported" });
40
+ if (navigator.clipboard) {
41
+ return navigator.clipboard.writeText(link).then(
42
+ () => ({ success: true, method: "clipboard" }),
43
+ () => ({ success: false, method: "unsupported" }),
44
+ );
45
+ }
46
+ return Promise.resolve({ success: false, method: "unsupported" });
47
47
  }
@@ -1,18 +1,18 @@
1
1
  export type NavigatorClipboardProps = {
2
- text: string;
2
+ text: string;
3
3
  };
4
4
 
5
5
  export type NavigatorShareProps = {
6
- title: string;
7
- text: string;
8
- link: string;
6
+ title: string;
7
+ text: string;
8
+ link: string;
9
9
  };
10
10
 
11
11
  export type NavigatorClipboardResult = {
12
- success: boolean;
12
+ success: boolean;
13
13
  };
14
14
 
15
15
  export type NavigatorShareResult = {
16
- success: boolean;
17
- method: "share" | "clipboard" | "unsupported";
16
+ success: boolean;
17
+ method: "share" | "clipboard" | "unsupported";
18
18
  };