@musecat/functionkit 1.0.1 → 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 (120) 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/README.md +18 -5
  41. package/index.ts +63 -68
  42. package/package.json +27 -13
  43. package/packages/components/ScrolltoTop.tsx +3 -3
  44. package/packages/components/SwitchCase.tsx +7 -7
  45. package/packages/components/ViewportPortal.tsx +23 -23
  46. package/packages/cookie/cookie.shared.ts +97 -101
  47. package/packages/datetime/dateTime.client.ts +86 -86
  48. package/packages/datetime/dateTime.server.ts +86 -86
  49. package/packages/datetime/dateTime.shared.ts +281 -281
  50. package/packages/hooks/useAvoidKeyboard.ts +15 -15
  51. package/packages/hooks/useCheckInvisible.ts +16 -16
  52. package/packages/hooks/useCheckScroll.ts +10 -10
  53. package/packages/hooks/useClientDateTime.ts +33 -33
  54. package/packages/hooks/useDebounce.ts +95 -95
  55. package/packages/hooks/useDebouncedCallback.ts +45 -45
  56. package/packages/hooks/useDoubleClick.ts +43 -43
  57. package/packages/hooks/useGeolocation.ts +130 -130
  58. package/packages/hooks/useHasMounted.ts +5 -5
  59. package/packages/hooks/useIntersectionObserver.ts +20 -20
  60. package/packages/hooks/useInterval.ts +46 -45
  61. package/packages/hooks/useKeyboardHeight.ts +9 -9
  62. package/packages/hooks/useKeyboardListNavigation.ts +156 -156
  63. package/packages/hooks/useLongPress.ts +98 -98
  64. package/packages/hooks/usePreservedCallback.ts +12 -12
  65. package/packages/hooks/usePreservedReference.ts +10 -10
  66. package/packages/hooks/useRefEffect.ts +19 -19
  67. package/packages/hooks/useRelativeDateTime.ts +37 -37
  68. package/packages/hooks/useTimeout.ts +41 -41
  69. package/packages/hooks/useToggleState.ts +5 -5
  70. package/packages/hooks/useViewportHeight.ts +13 -13
  71. package/packages/hooks/useViewportMatch.ts +16 -22
  72. package/packages/utils/browserStorage.ts +26 -26
  73. package/packages/utils/buildContext.tsx +10 -10
  74. package/packages/utils/checkDevice.ts +64 -64
  75. package/packages/utils/clipboardShare.tsx +31 -31
  76. package/packages/utils/clipboardShare.types.ts +7 -7
  77. package/packages/utils/floatingMotion.ts +71 -71
  78. package/packages/utils/keyboardTarget.ts +9 -9
  79. package/packages/utils/mergeRefs.ts +9 -9
  80. package/packages/utils/seen.ts +15 -15
  81. package/packages/utils/subscribeKeyboardHeight.ts +46 -46
  82. package/tests/components/ScrolltoTop.test.tsx +15 -0
  83. package/tests/components/SwitchCase.test.tsx +34 -0
  84. package/tests/components/ViewportPortal.test.tsx +42 -0
  85. package/tests/cookie/cookie.test.ts +156 -0
  86. package/tests/datetime/datetime.test.ts +250 -0
  87. package/tests/hooks/useAvoidKeyboard.test.ts +12 -0
  88. package/tests/hooks/useCheckInvisible.test.ts +28 -0
  89. package/tests/hooks/useCheckScroll.test.ts +23 -0
  90. package/tests/hooks/useClientDateTime.test.ts +14 -0
  91. package/tests/hooks/useDebounce.test.ts +71 -0
  92. package/tests/hooks/useDebouncedCallback.test.ts +34 -0
  93. package/tests/hooks/useDoubleClick.test.ts +31 -0
  94. package/tests/hooks/useGeolocation.test.ts +50 -0
  95. package/tests/hooks/useHasMounted.test.ts +10 -0
  96. package/tests/hooks/useIntersectionObserver.test.ts +13 -0
  97. package/tests/hooks/useInterval.test.ts +31 -0
  98. package/tests/hooks/useKeyboardHeight.test.ts +10 -0
  99. package/tests/hooks/useKeyboardListNavigation.test.ts +82 -0
  100. package/tests/hooks/useLongPress.test.ts +52 -0
  101. package/tests/hooks/usePreservedCallback.test.ts +35 -0
  102. package/tests/hooks/usePreservedReference.test.ts +35 -0
  103. package/tests/hooks/useRefEffect.test.ts +30 -0
  104. package/tests/hooks/useRelativeDateTime.test.ts +19 -0
  105. package/tests/hooks/useTimeout.test.ts +34 -0
  106. package/tests/hooks/useToggleState.test.ts +35 -0
  107. package/tests/hooks/useViewportHeight.test.ts +10 -0
  108. package/tests/hooks/useViewportMatch.test.ts +35 -0
  109. package/tests/setup.ts +9 -0
  110. package/tests/utils/browserStorage.test.ts +54 -0
  111. package/tests/utils/buildContext.test.tsx +27 -0
  112. package/tests/utils/checkDevice.test.ts +84 -0
  113. package/tests/utils/clipboardShare.test.ts +78 -0
  114. package/tests/utils/floatingMotion.test.ts +84 -0
  115. package/tests/utils/keyboardTarget.test.ts +44 -0
  116. package/tests/utils/mergeRefs.test.ts +18 -0
  117. package/tests/utils/seen.test.ts +31 -0
  118. package/tests/utils/subscribeKeyboardHeight.test.ts +50 -0
  119. package/tsconfig.json +16 -16
  120. package/vitest.config.ts +16 -0
@@ -3,26 +3,26 @@
3
3
  import { useKeyboardHeight } from "./useKeyboardHeight";
4
4
 
5
5
  type UseAvoidKeyboardOptions = {
6
- safeAreaBottom?: number;
7
- transitionDuration?: number;
8
- transitionTimingFunction?: string;
6
+ safeAreaBottom?: number;
7
+ transitionDuration?: number;
8
+ transitionTimingFunction?: string;
9
9
  };
10
10
 
11
11
  export function useAvoidKeyboard(options?: UseAvoidKeyboardOptions) {
12
- const {
13
- safeAreaBottom = 0,
14
- transitionDuration = 200,
15
- transitionTimingFunction = "ease-out",
16
- } = options ?? {};
12
+ const {
13
+ safeAreaBottom = 0,
14
+ transitionDuration = 200,
15
+ transitionTimingFunction = "ease-out",
16
+ } = options ?? {};
17
17
 
18
- const { keyboardHeight } = useKeyboardHeight();
18
+ const { keyboardHeight } = useKeyboardHeight();
19
19
 
20
- const offset = keyboardHeight > 0 ? keyboardHeight : safeAreaBottom;
20
+ const offset = keyboardHeight > 0 ? keyboardHeight : safeAreaBottom;
21
21
 
22
- const style: React.CSSProperties = {
23
- transform: offset > 0 ? `translateY(-${offset}px)` : undefined,
24
- transition: `transform ${transitionDuration}ms ${transitionTimingFunction}`,
25
- };
22
+ const style: React.CSSProperties = {
23
+ transform: offset > 0 ? `translateY(-${offset}px)` : undefined,
24
+ transition: `transform ${transitionDuration}ms ${transitionTimingFunction}`,
25
+ };
26
26
 
27
- return { style };
27
+ return { style };
28
28
  }
@@ -3,23 +3,23 @@
3
3
  import { useEffect, useState } from "react";
4
4
 
5
5
  export function useCheckInvisible(className: string) {
6
- const [isInvisible, setIsInvisible] = useState(false);
6
+ const [isInvisible, setIsInvisible] = useState(false);
7
7
 
8
- useEffect(() => {
9
- const handleScroll = () => {
10
- const el = document.querySelector(`.${className}`);
11
- if (el) {
12
- const { top } = el.getBoundingClientRect();
13
- setIsInvisible(top < 0);
14
- } else {
15
- setIsInvisible(false);
16
- }
17
- };
8
+ useEffect(() => {
9
+ const handleScroll = () => {
10
+ const el = document.querySelector(`.${className}`);
11
+ if (el) {
12
+ const { top } = el.getBoundingClientRect();
13
+ setIsInvisible(top < 0);
14
+ } else {
15
+ setIsInvisible(false);
16
+ }
17
+ };
18
18
 
19
- handleScroll();
20
- window.addEventListener("scroll", handleScroll);
21
- return () => window.removeEventListener("scroll", handleScroll);
22
- }, [className]);
19
+ handleScroll();
20
+ window.addEventListener("scroll", handleScroll);
21
+ return () => window.removeEventListener("scroll", handleScroll);
22
+ }, [className]);
23
23
 
24
- return { isInvisible };
24
+ return { isInvisible };
25
25
  }
@@ -3,17 +3,17 @@
3
3
  import { useEffect, useState } from "react";
4
4
 
5
5
  export function useCheckScroll() {
6
- const [isScrolled, setIsScrolled] = useState(false);
6
+ const [isScrolled, setIsScrolled] = useState(false);
7
7
 
8
- useEffect(() => {
9
- const handleScroll = () => {
10
- setIsScrolled(window.scrollY > 0);
11
- };
8
+ useEffect(() => {
9
+ const handleScroll = () => {
10
+ setIsScrolled(window.scrollY > 0);
11
+ };
12
12
 
13
- handleScroll();
14
- window.addEventListener("scroll", handleScroll);
15
- return () => window.removeEventListener("scroll", handleScroll);
16
- }, []);
13
+ handleScroll();
14
+ window.addEventListener("scroll", handleScroll);
15
+ return () => window.removeEventListener("scroll", handleScroll);
16
+ }, []);
17
17
 
18
- return { isScrolled };
18
+ return { isScrolled };
19
19
  }
@@ -3,47 +3,47 @@
3
3
  import { useEffect, useState } from "react";
4
4
  import { formatClientDateTime } from "../datetime/dateTime.client";
5
5
  import type {
6
- DateInput,
7
- DatePreset,
8
- TimePreset,
6
+ DateInput,
7
+ DatePreset,
8
+ TimePreset,
9
9
  } from "../datetime/dateTime.shared";
10
10
  import { toDate } from "../datetime/dateTime.shared";
11
11
 
12
12
  type UseClientDateTimeOptions = {
13
- locale?: string;
14
- timeZone?: string;
15
- datePreset?: DatePreset;
16
- timePreset?: TimePreset;
13
+ locale?: string;
14
+ timeZone?: string;
15
+ datePreset?: DatePreset;
16
+ timePreset?: TimePreset;
17
17
  };
18
18
 
19
19
  export function useClientDateTime(
20
- value: DateInput,
21
- options?: UseClientDateTimeOptions,
20
+ value: DateInput,
21
+ options?: UseClientDateTimeOptions,
22
22
  ) {
23
- const [ready, setReady] = useState(false);
24
- const [text, setText] = useState("");
23
+ const [ready, setReady] = useState(false);
24
+ const [text, setText] = useState("");
25
25
 
26
- useEffect(() => {
27
- setText(
28
- formatClientDateTime(value, {
29
- locale: options?.locale,
30
- timeZone: options?.timeZone,
31
- datePreset: options?.datePreset,
32
- timePreset: options?.timePreset,
33
- }),
34
- );
35
- setReady(true);
36
- }, [
37
- options?.datePreset,
38
- options?.locale,
39
- options?.timePreset,
40
- options?.timeZone,
41
- value,
42
- ]);
26
+ useEffect(() => {
27
+ setText(
28
+ formatClientDateTime(value, {
29
+ locale: options?.locale,
30
+ timeZone: options?.timeZone,
31
+ datePreset: options?.datePreset,
32
+ timePreset: options?.timePreset,
33
+ }),
34
+ );
35
+ setReady(true);
36
+ }, [
37
+ options?.datePreset,
38
+ options?.locale,
39
+ options?.timePreset,
40
+ options?.timeZone,
41
+ value,
42
+ ]);
43
43
 
44
- return {
45
- ready,
46
- text,
47
- date: toDate(value),
48
- };
44
+ return {
45
+ ready,
46
+ text,
47
+ date: toDate(value),
48
+ };
49
49
  }
@@ -1,118 +1,118 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useMemo, useRef } from "react";
3
+ import { useEffect, useMemo } from "react";
4
4
 
5
5
  import { usePreservedCallback } from "./usePreservedCallback";
6
6
 
7
7
  export function debounce<F extends (...args: unknown[]) => void>(
8
- func: F,
9
- debounceMs: number,
10
- {
11
- edges = ["leading", "trailing"],
12
- }: { edges?: Array<"leading" | "trailing"> } = {},
8
+ func: F,
9
+ debounceMs: number,
10
+ {
11
+ edges = ["leading", "trailing"],
12
+ }: { edges?: Array<"leading" | "trailing"> } = {},
13
13
  ): {
14
- (...args: Parameters<F>): void;
15
- cancel: () => void;
14
+ (...args: Parameters<F>): void;
15
+ cancel: () => void;
16
16
  } {
17
- let pendingThis: ThisParameterType<F> | undefined;
18
- let pendingArgs: Parameters<F> | null = null;
19
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
20
-
21
- const leading = edges.includes("leading");
22
- const trailing = edges.includes("trailing");
23
-
24
- const invoke = () => {
25
- if (pendingArgs !== null) {
26
- func.apply(pendingThis, pendingArgs);
27
- pendingThis = undefined;
28
- pendingArgs = null;
29
- }
30
- };
31
-
32
- const cancelTimer = () => {
33
- if (timeoutId !== null) {
34
- clearTimeout(timeoutId);
35
- timeoutId = null;
36
- }
37
- };
38
-
39
- const cancel = () => {
40
- cancelTimer();
41
- pendingThis = undefined;
42
- pendingArgs = null;
43
- };
44
-
45
- const schedule = () => {
46
- cancelTimer();
47
- timeoutId = setTimeout(() => {
48
- timeoutId = null;
49
- if (trailing) invoke();
50
- cancel();
51
- }, debounceMs);
52
- };
53
-
54
- const debounced = function (
55
- this: ThisParameterType<F>,
56
- ...args: Parameters<F>
57
- ) {
58
- pendingThis = this;
59
- pendingArgs = args;
60
-
61
- const isFirstCall = timeoutId == null;
62
-
63
- schedule();
64
-
65
- if (leading && isFirstCall) {
66
- invoke();
67
- }
68
- } as {
69
- (...args: Parameters<F>): void;
70
- cancel: () => void;
71
- };
72
-
73
- debounced.cancel = cancel;
74
- return debounced;
17
+ let pendingThis: ThisParameterType<F> | undefined;
18
+ let pendingArgs: Parameters<F> | null = null;
19
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
20
+
21
+ const leading = edges.includes("leading");
22
+ const trailing = edges.includes("trailing");
23
+
24
+ const invoke = () => {
25
+ if (pendingArgs !== null) {
26
+ func.apply(pendingThis, pendingArgs);
27
+ pendingThis = undefined;
28
+ pendingArgs = null;
29
+ }
30
+ };
31
+
32
+ const cancelTimer = () => {
33
+ if (timeoutId !== null) {
34
+ clearTimeout(timeoutId);
35
+ timeoutId = null;
36
+ }
37
+ };
38
+
39
+ const cancel = () => {
40
+ cancelTimer();
41
+ pendingThis = undefined;
42
+ pendingArgs = null;
43
+ };
44
+
45
+ const schedule = () => {
46
+ cancelTimer();
47
+ timeoutId = setTimeout(() => {
48
+ timeoutId = null;
49
+ if (trailing) invoke();
50
+ cancel();
51
+ }, debounceMs);
52
+ };
53
+
54
+ const debounced = function (
55
+ this: ThisParameterType<F>,
56
+ ...args: Parameters<F>
57
+ ) {
58
+ pendingThis = this;
59
+ pendingArgs = args;
60
+
61
+ const isFirstCall = timeoutId == null;
62
+
63
+ schedule();
64
+
65
+ if (leading && isFirstCall) {
66
+ invoke();
67
+ }
68
+ } as {
69
+ (...args: Parameters<F>): void;
70
+ cancel: () => void;
71
+ };
72
+
73
+ debounced.cancel = cancel;
74
+ return debounced;
75
75
  }
76
76
 
77
77
  type DebounceOptions = {
78
- leading?: boolean;
79
- trailing?: boolean;
78
+ leading?: boolean;
79
+ trailing?: boolean;
80
80
  };
81
81
 
82
82
  export function useDebounce<F extends (...args: unknown[]) => void>(
83
- callback: F,
84
- wait: number,
85
- options: DebounceOptions = {},
83
+ callback: F,
84
+ wait: number,
85
+ options: DebounceOptions = {},
86
86
  ) {
87
- const preservedCallback = usePreservedCallback(callback) as F;
87
+ const preservedCallback = usePreservedCallback(callback) as F;
88
88
 
89
- const { leading = false, trailing = true } = options;
89
+ const { leading = false, trailing = true } = options;
90
90
 
91
- const edges = useMemo(() => {
92
- const _edges: Array<"leading" | "trailing"> = [];
93
- if (leading) {
94
- _edges.push("leading");
95
- }
91
+ const edges = useMemo(() => {
92
+ const _edges: Array<"leading" | "trailing"> = [];
93
+ if (leading) {
94
+ _edges.push("leading");
95
+ }
96
96
 
97
- if (trailing) {
98
- _edges.push("trailing");
99
- }
97
+ if (trailing) {
98
+ _edges.push("trailing");
99
+ }
100
100
 
101
- return _edges;
102
- }, [leading, trailing]);
101
+ return _edges;
102
+ }, [leading, trailing]);
103
103
 
104
- const debounced = useMemo(() => {
105
- return debounce(preservedCallback, wait, { edges });
106
- }, [preservedCallback, wait, edges]);
104
+ const debounced = useMemo(() => {
105
+ return debounce(preservedCallback, wait, { edges });
106
+ }, [preservedCallback, wait, edges]);
107
107
 
108
- useEffect(
109
- function cancelDebouncedOnUnmount() {
110
- return () => {
111
- debounced.cancel();
112
- };
113
- },
114
- [debounced],
115
- );
108
+ useEffect(
109
+ function cancelDebouncedOnUnmount() {
110
+ return () => {
111
+ debounced.cancel();
112
+ };
113
+ },
114
+ [debounced],
115
+ );
116
116
 
117
- return debounced;
117
+ return debounced;
118
118
  }
@@ -6,64 +6,64 @@ import { debounce } from "./useDebounce";
6
6
  import { usePreservedCallback } from "./usePreservedCallback";
7
7
 
8
8
  type DebounceOptions = {
9
- leading?: boolean;
10
- trailing?: boolean;
9
+ leading?: boolean;
10
+ trailing?: boolean;
11
11
  };
12
12
 
13
13
  export function useDebouncedCallback({
14
- onChange,
15
- timeThreshold,
16
- leading = false,
17
- trailing = true,
14
+ onChange,
15
+ timeThreshold,
16
+ leading = false,
17
+ trailing = true,
18
18
  }: DebounceOptions & {
19
- onChange: (newValue: boolean) => void;
20
- timeThreshold: number;
19
+ onChange: (newValue: boolean) => void;
20
+ timeThreshold: number;
21
21
  }) {
22
- const handleChange = usePreservedCallback(onChange);
23
- const ref = useRef({ value: false, clearPreviousDebounce: () => {} });
22
+ const handleChange = usePreservedCallback(onChange);
23
+ const ref = useRef({ value: false, clearPreviousDebounce: () => {} });
24
24
 
25
- useEffect(function clearDebouncedOnUnmount() {
26
- const current = ref.current;
27
- return () => {
28
- current.clearPreviousDebounce();
29
- };
30
- }, []);
25
+ useEffect(function clearDebouncedOnUnmount() {
26
+ const current = ref.current;
27
+ return () => {
28
+ current.clearPreviousDebounce();
29
+ };
30
+ }, []);
31
31
 
32
- const edges = useMemo(() => {
33
- const _edges: Array<"leading" | "trailing"> = [];
34
- if (leading) {
35
- _edges.push("leading");
36
- }
32
+ const edges = useMemo(() => {
33
+ const _edges: Array<"leading" | "trailing"> = [];
34
+ if (leading) {
35
+ _edges.push("leading");
36
+ }
37
37
 
38
- if (trailing) {
39
- _edges.push("trailing");
40
- }
38
+ if (trailing) {
39
+ _edges.push("trailing");
40
+ }
41
41
 
42
- return _edges;
43
- }, [leading, trailing]);
42
+ return _edges;
43
+ }, [leading, trailing]);
44
44
 
45
- return useCallback(
46
- (nextValue: boolean) => {
47
- if (nextValue === ref.current.value) {
48
- return;
49
- }
45
+ return useCallback(
46
+ (nextValue: boolean) => {
47
+ if (nextValue === ref.current.value) {
48
+ return;
49
+ }
50
50
 
51
- const debounced = debounce(
52
- () => {
53
- handleChange(nextValue);
51
+ const debounced = debounce(
52
+ () => {
53
+ handleChange(nextValue);
54
54
 
55
- ref.current.value = nextValue;
56
- },
57
- timeThreshold,
58
- { edges },
59
- );
55
+ ref.current.value = nextValue;
56
+ },
57
+ timeThreshold,
58
+ { edges },
59
+ );
60
60
 
61
- ref.current.clearPreviousDebounce();
61
+ ref.current.clearPreviousDebounce();
62
62
 
63
- debounced();
63
+ debounced();
64
64
 
65
- ref.current.clearPreviousDebounce = debounced.cancel;
66
- },
67
- [handleChange, timeThreshold, edges],
68
- );
65
+ ref.current.clearPreviousDebounce = debounced.cancel;
66
+ },
67
+ [handleChange, timeThreshold, edges],
68
+ );
69
69
  }
@@ -3,51 +3,51 @@
3
3
  import { type MouseEvent, useCallback, useEffect, useRef } from "react";
4
4
 
5
5
  type UseDoubleClickProps<E extends HTMLElement> = {
6
- delay?: number;
7
- click?: (event: MouseEvent<E>) => void;
8
- doubleClick: (event: MouseEvent<E>) => void;
6
+ delay?: number;
7
+ click?: (event: MouseEvent<E>) => void;
8
+ doubleClick: (event: MouseEvent<E>) => void;
9
9
  };
10
10
 
11
11
  export function useDoubleClick<E extends HTMLElement = HTMLElement>({
12
- delay = 250,
13
- click,
14
- doubleClick,
12
+ delay = 250,
13
+ click,
14
+ doubleClick,
15
15
  }: UseDoubleClickProps<E>) {
16
- const clickTimeout = useRef<number | null>(null);
17
-
18
- const savedClick = useRef(click);
19
- const savedDoubleClick = useRef(doubleClick);
20
- savedClick.current = click;
21
- savedDoubleClick.current = doubleClick;
22
-
23
- useEffect(() => {
24
- return () => {
25
- if (clickTimeout.current != null) {
26
- window.clearTimeout(clickTimeout.current);
27
- }
28
- };
29
- }, []);
30
-
31
- const handleEvent = useCallback(
32
- (event: MouseEvent<E>) => {
33
- if (clickTimeout.current != null) {
34
- window.clearTimeout(clickTimeout.current);
35
- clickTimeout.current = null;
36
- }
37
-
38
- if (event.detail === 1 && savedClick.current) {
39
- clickTimeout.current = window.setTimeout(() => {
40
- clickTimeout.current = null;
41
- savedClick.current?.(event);
42
- }, delay);
43
- }
44
-
45
- if (event.detail === 2) {
46
- savedDoubleClick.current(event);
47
- }
48
- },
49
- [delay],
50
- );
51
-
52
- return handleEvent;
16
+ const clickTimeout = useRef<number | null>(null);
17
+
18
+ const savedClick = useRef(click);
19
+ const savedDoubleClick = useRef(doubleClick);
20
+ savedClick.current = click;
21
+ savedDoubleClick.current = doubleClick;
22
+
23
+ useEffect(() => {
24
+ return () => {
25
+ if (clickTimeout.current != null) {
26
+ window.clearTimeout(clickTimeout.current);
27
+ }
28
+ };
29
+ }, []);
30
+
31
+ const handleEvent = useCallback(
32
+ (event: MouseEvent<E>) => {
33
+ if (clickTimeout.current != null) {
34
+ window.clearTimeout(clickTimeout.current);
35
+ clickTimeout.current = null;
36
+ }
37
+
38
+ if (event.detail === 1 && savedClick.current) {
39
+ clickTimeout.current = window.setTimeout(() => {
40
+ clickTimeout.current = null;
41
+ savedClick.current?.(event);
42
+ }, delay);
43
+ }
44
+
45
+ if (event.detail === 2) {
46
+ savedDoubleClick.current(event);
47
+ }
48
+ },
49
+ [delay],
50
+ );
51
+
52
+ return handleEvent;
53
53
  }