@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
@@ -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(`.${CSS.escape(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
  }
@@ -2,48 +2,35 @@
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 = {
13
- locale?: string;
14
- timeZone?: string;
15
- datePreset?: DatePreset;
16
- timePreset?: TimePreset;
9
+ locale?: string;
10
+ timeZone?: string;
11
+ datePreset?: DatePreset;
12
+ timePreset?: TimePreset;
17
13
  };
18
14
 
19
- export function useClientDateTime(
20
- value: DateInput,
21
- options?: UseClientDateTimeOptions,
22
- ) {
23
- const [ready, setReady] = useState(false);
24
- const [text, setText] = useState("");
15
+ export function useClientDateTime(value: DateInput, options?: UseClientDateTimeOptions) {
16
+ const [ready, setReady] = useState(false);
17
+ const [text, setText] = useState("");
25
18
 
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
- ]);
19
+ useEffect(() => {
20
+ setText(
21
+ formatClientDateTime(value, {
22
+ locale: options?.locale,
23
+ timeZone: options?.timeZone,
24
+ datePreset: options?.datePreset,
25
+ timePreset: options?.timePreset,
26
+ }),
27
+ );
28
+ setReady(true);
29
+ }, [options?.datePreset, options?.locale, options?.timePreset, options?.timeZone, value]);
43
30
 
44
- return {
45
- ready,
46
- text,
47
- date: toDate(value),
48
- };
31
+ return {
32
+ ready,
33
+ text,
34
+ date: toDate(value),
35
+ };
49
36
  }
@@ -1,118 +1,113 @@
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
+ { edges = ["leading", "trailing"] }: { edges?: Array<"leading" | "trailing"> } = {},
13
11
  ): {
14
- (...args: Parameters<F>): void;
15
- cancel: () => void;
12
+ (...args: Parameters<F>): void;
13
+ cancel: () => void;
16
14
  } {
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;
15
+ let pendingThis: ThisParameterType<F> | undefined;
16
+ let pendingArgs: Parameters<F> | null = null;
17
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
18
+
19
+ const leading = edges.includes("leading");
20
+ const trailing = edges.includes("trailing");
21
+
22
+ const invoke = () => {
23
+ if (pendingArgs !== null) {
24
+ func.apply(pendingThis, pendingArgs);
25
+ pendingThis = undefined;
26
+ pendingArgs = null;
27
+ }
28
+ };
29
+
30
+ const cancelTimer = () => {
31
+ if (timeoutId !== null) {
32
+ clearTimeout(timeoutId);
33
+ timeoutId = null;
34
+ }
35
+ };
36
+
37
+ const cancel = () => {
38
+ cancelTimer();
39
+ pendingThis = undefined;
40
+ pendingArgs = null;
41
+ };
42
+
43
+ const schedule = () => {
44
+ cancelTimer();
45
+ timeoutId = setTimeout(() => {
46
+ timeoutId = null;
47
+ if (trailing) invoke();
48
+ cancel();
49
+ }, debounceMs);
50
+ };
51
+
52
+ const debounced = function (this: ThisParameterType<F>, ...args: Parameters<F>) {
53
+ pendingThis = this;
54
+ pendingArgs = args;
55
+
56
+ const isFirstCall = timeoutId == null;
57
+
58
+ schedule();
59
+
60
+ if (leading && isFirstCall) {
61
+ invoke();
62
+ }
63
+ } as {
64
+ (...args: Parameters<F>): void;
65
+ cancel: () => void;
66
+ };
67
+
68
+ debounced.cancel = cancel;
69
+ return debounced;
75
70
  }
76
71
 
77
72
  type DebounceOptions = {
78
- leading?: boolean;
79
- trailing?: boolean;
73
+ leading?: boolean;
74
+ trailing?: boolean;
80
75
  };
81
76
 
82
77
  export function useDebounce<F extends (...args: unknown[]) => void>(
83
- callback: F,
84
- wait: number,
85
- options: DebounceOptions = {},
78
+ callback: F,
79
+ wait: number,
80
+ options: DebounceOptions = {},
86
81
  ) {
87
- const preservedCallback = usePreservedCallback(callback) as F;
82
+ const preservedCallback = usePreservedCallback(callback) as F;
88
83
 
89
- const { leading = false, trailing = true } = options;
84
+ const { leading = false, trailing = true } = options;
90
85
 
91
- const edges = useMemo(() => {
92
- const _edges: Array<"leading" | "trailing"> = [];
93
- if (leading) {
94
- _edges.push("leading");
95
- }
86
+ const edges = useMemo(() => {
87
+ const _edges: Array<"leading" | "trailing"> = [];
88
+ if (leading) {
89
+ _edges.push("leading");
90
+ }
96
91
 
97
- if (trailing) {
98
- _edges.push("trailing");
99
- }
92
+ if (trailing) {
93
+ _edges.push("trailing");
94
+ }
100
95
 
101
- return _edges;
102
- }, [leading, trailing]);
96
+ return _edges;
97
+ }, [leading, trailing]);
103
98
 
104
- const debounced = useMemo(() => {
105
- return debounce(preservedCallback, wait, { edges });
106
- }, [preservedCallback, wait, edges]);
99
+ const debounced = useMemo(() => {
100
+ return debounce(preservedCallback, wait, { edges });
101
+ }, [preservedCallback, wait, edges]);
107
102
 
108
- useEffect(
109
- function cancelDebouncedOnUnmount() {
110
- return () => {
111
- debounced.cancel();
112
- };
113
- },
114
- [debounced],
115
- );
103
+ useEffect(
104
+ function cancelDebouncedOnUnmount() {
105
+ return () => {
106
+ debounced.cancel();
107
+ };
108
+ },
109
+ [debounced],
110
+ );
116
111
 
117
- return debounced;
112
+ return debounced;
118
113
  }
@@ -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
  }