@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.
- package/.agents/references/components/ScrolltoTop.md +36 -0
- package/.agents/references/components/SwitchCase.md +47 -0
- package/.agents/references/components/ViewportPortal.md +34 -0
- package/.agents/references/cookie/cookie.md +47 -0
- package/.agents/references/datetime/dateTime.client.md +42 -0
- package/.agents/references/datetime/dateTime.server.md +42 -0
- package/.agents/references/datetime/dateTime.shared.md +86 -0
- package/.agents/references/hooks/useAvoidKeyboard.md +29 -0
- package/.agents/references/hooks/useCheckInvisible.md +36 -0
- package/.agents/references/hooks/useCheckScroll.md +36 -0
- package/.agents/references/hooks/useClientDateTime.md +39 -0
- package/.agents/references/hooks/useDebounce.md +46 -0
- package/.agents/references/hooks/useDebouncedCallback.md +34 -0
- package/.agents/references/hooks/useDoubleClick.md +40 -0
- package/.agents/references/hooks/useGeolocation.md +37 -0
- package/.agents/references/hooks/useHasMounted.md +27 -0
- package/.agents/references/hooks/useIntersectionObserver.md +41 -0
- package/.agents/references/hooks/useInterval.md +44 -0
- package/.agents/references/hooks/useKeyboardHeight.md +33 -0
- package/.agents/references/hooks/useKeyboardListNavigation.md +58 -0
- package/.agents/references/hooks/useLongPress.md +48 -0
- package/.agents/references/hooks/usePreservedCallback.md +37 -0
- package/.agents/references/hooks/usePreservedReference.md +34 -0
- package/.agents/references/hooks/useRefEffect.md +35 -0
- package/.agents/references/hooks/useRelativeDateTime.md +35 -0
- package/.agents/references/hooks/useTimeout.md +42 -0
- package/.agents/references/hooks/useToggleState.md +39 -0
- package/.agents/references/hooks/useViewportHeight.md +26 -0
- package/.agents/references/hooks/useViewportMatch.md +32 -0
- package/.agents/references/utils/browserStorage.md +41 -0
- package/.agents/references/utils/buildContext.md +47 -0
- package/.agents/references/utils/clipboardShare.md +59 -0
- package/.agents/references/utils/floatingMotion.md +67 -0
- package/.agents/references/utils/getDeviceInfo.md +43 -0
- package/.agents/references/utils/isEditableKeyboardTarget.md +35 -0
- package/.agents/references/utils/mergeRefs.md +29 -0
- package/.agents/references/utils/seen.md +37 -0
- package/.agents/references/utils/subscribeKeyboardHeight.md +35 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- package/.github/workflows/ci.yml +20 -0
- package/.local/state/gh/device-id +1 -0
- package/.prettierrc +8 -0
- package/AGENTS.md +117 -396
- package/CODE_OF_CONDUCT.md +55 -0
- package/CONTRIBUTING.md +39 -0
- package/README.md +18 -5
- package/biome.json +25 -0
- package/index.ts +63 -68
- package/package.json +46 -13
- package/packages/components/ScrolltoTop.tsx +3 -3
- package/packages/components/SwitchCase.tsx +7 -7
- package/packages/components/ViewportPortal.tsx +22 -23
- package/packages/cookie/cookie.shared.ts +80 -110
- package/packages/cookie/cookieNames.shared.ts +9 -0
- package/packages/datetime/dateTime.client.ts +84 -86
- package/packages/datetime/dateTime.server.ts +84 -86
- package/packages/datetime/dateTime.shared.ts +249 -287
- package/packages/hooks/useAvoidKeyboard.ts +15 -15
- package/packages/hooks/useCheckInvisible.ts +16 -16
- package/packages/hooks/useCheckScroll.ts +10 -10
- package/packages/hooks/useClientDateTime.ts +24 -37
- package/packages/hooks/useDebounce.ts +90 -95
- package/packages/hooks/useDebouncedCallback.ts +45 -45
- package/packages/hooks/useDoubleClick.ts +43 -43
- package/packages/hooks/useGeolocation.ts +126 -130
- package/packages/hooks/useHasMounted.ts +5 -5
- package/packages/hooks/useIntersectionObserver.ts +20 -20
- package/packages/hooks/useInterval.ts +45 -45
- package/packages/hooks/useKeyboardHeight.ts +9 -9
- package/packages/hooks/useKeyboardListNavigation.ts +130 -158
- package/packages/hooks/useLongPress.ts +87 -98
- package/packages/hooks/usePreservedCallback.ts +12 -12
- package/packages/hooks/usePreservedReference.ts +10 -10
- package/packages/hooks/useRefEffect.ts +19 -19
- package/packages/hooks/useRelativeDateTime.ts +36 -39
- package/packages/hooks/useTimeout.ts +41 -41
- package/packages/hooks/useToggleState.ts +5 -5
- package/packages/hooks/useViewportHeight.ts +12 -13
- package/packages/hooks/useViewportMatch.ts +13 -23
- package/packages/utils/browserStorage.ts +26 -26
- package/packages/utils/buildContext.tsx +10 -11
- package/packages/utils/checkDevice.ts +64 -64
- package/packages/utils/clipboardShare.tsx +31 -31
- package/packages/utils/clipboardShare.types.ts +7 -7
- package/packages/utils/floatingMotion.ts +71 -77
- package/packages/utils/keyboardTarget.ts +9 -9
- package/packages/utils/mergeRefs.ts +9 -9
- package/packages/utils/seen.ts +15 -15
- package/packages/utils/subscribeKeyboardHeight.ts +49 -46
- package/tests/components/ScrolltoTop.test.tsx +15 -0
- package/tests/components/SwitchCase.test.tsx +33 -0
- package/tests/components/ViewportPortal.test.tsx +46 -0
- package/tests/cookie/cookie.test.ts +192 -0
- package/tests/datetime/datetime.test.ts +461 -0
- package/tests/hooks/useAvoidKeyboard.test.ts +43 -0
- package/tests/hooks/useCheckInvisible.test.ts +59 -0
- package/tests/hooks/useCheckScroll.test.ts +23 -0
- package/tests/hooks/useClientDateTime.test.ts +12 -0
- package/tests/hooks/useDebounce.test.ts +96 -0
- package/tests/hooks/useDebouncedCallback.test.ts +63 -0
- package/tests/hooks/useDoubleClick.test.ts +82 -0
- package/tests/hooks/useGeolocation.test.ts +201 -0
- package/tests/hooks/useHasMounted.test.ts +10 -0
- package/tests/hooks/useIntersectionObserver.test.ts +78 -0
- package/tests/hooks/useInterval.test.ts +56 -0
- package/tests/hooks/useKeyboardHeight.test.ts +10 -0
- package/tests/hooks/useKeyboardListNavigation.test.ts +404 -0
- package/tests/hooks/useLongPress.test.ts +214 -0
- package/tests/hooks/usePreservedCallback.test.ts +33 -0
- package/tests/hooks/usePreservedReference.test.ts +42 -0
- package/tests/hooks/useRefEffect.test.ts +67 -0
- package/tests/hooks/useRelativeDateTime.test.ts +21 -0
- package/tests/hooks/useTimeout.test.ts +72 -0
- package/tests/hooks/useToggleState.test.ts +43 -0
- package/tests/hooks/useViewportHeight.test.ts +32 -0
- package/tests/hooks/useViewportMatch.test.ts +35 -0
- package/tests/setup.ts +9 -0
- package/tests/utils/browserStorage.test.ts +114 -0
- package/tests/utils/buildContext.test.tsx +26 -0
- package/tests/utils/checkDevice.test.ts +140 -0
- package/tests/utils/clipboardShare.test.ts +152 -0
- package/tests/utils/floatingMotion.test.ts +135 -0
- package/tests/utils/keyboardTarget.test.ts +85 -0
- package/tests/utils/mergeRefs.test.ts +50 -0
- package/tests/utils/seen.test.ts +40 -0
- package/tests/utils/subscribeKeyboardHeight.test.ts +172 -0
- package/tsconfig.json +16 -16
- 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
|
-
|
|
6
|
+
const [isInvisible, setIsInvisible] = useState(false);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
handleScroll();
|
|
20
|
+
window.addEventListener("scroll", handleScroll);
|
|
21
|
+
return () => window.removeEventListener("scroll", handleScroll);
|
|
22
|
+
}, [className]);
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
return { isInvisible };
|
|
25
25
|
}
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
|
|
5
5
|
export function useCheckScroll() {
|
|
6
|
-
|
|
6
|
+
const [isScrolled, setIsScrolled] = useState(false);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const handleScroll = () => {
|
|
10
|
+
setIsScrolled(window.scrollY > 0);
|
|
11
|
+
};
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
handleScroll();
|
|
14
|
+
window.addEventListener("scroll", handleScroll);
|
|
15
|
+
return () => window.removeEventListener("scroll", handleScroll);
|
|
16
|
+
}, []);
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
locale?: string;
|
|
10
|
+
timeZone?: string;
|
|
11
|
+
datePreset?: DatePreset;
|
|
12
|
+
timePreset?: TimePreset;
|
|
17
13
|
};
|
|
18
14
|
|
|
19
|
-
export function useClientDateTime(
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
15
|
-
|
|
12
|
+
(...args: Parameters<F>): void;
|
|
13
|
+
cancel: () => void;
|
|
16
14
|
} {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
79
|
-
|
|
73
|
+
leading?: boolean;
|
|
74
|
+
trailing?: boolean;
|
|
80
75
|
};
|
|
81
76
|
|
|
82
77
|
export function useDebounce<F extends (...args: unknown[]) => void>(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
callback: F,
|
|
79
|
+
wait: number,
|
|
80
|
+
options: DebounceOptions = {},
|
|
86
81
|
) {
|
|
87
|
-
|
|
82
|
+
const preservedCallback = usePreservedCallback(callback) as F;
|
|
88
83
|
|
|
89
|
-
|
|
84
|
+
const { leading = false, trailing = true } = options;
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
const edges = useMemo(() => {
|
|
87
|
+
const _edges: Array<"leading" | "trailing"> = [];
|
|
88
|
+
if (leading) {
|
|
89
|
+
_edges.push("leading");
|
|
90
|
+
}
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
if (trailing) {
|
|
93
|
+
_edges.push("trailing");
|
|
94
|
+
}
|
|
100
95
|
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
return _edges;
|
|
97
|
+
}, [leading, trailing]);
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
const debounced = useMemo(() => {
|
|
100
|
+
return debounce(preservedCallback, wait, { edges });
|
|
101
|
+
}, [preservedCallback, wait, edges]);
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
useEffect(
|
|
104
|
+
function cancelDebouncedOnUnmount() {
|
|
105
|
+
return () => {
|
|
106
|
+
debounced.cancel();
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
[debounced],
|
|
110
|
+
);
|
|
116
111
|
|
|
117
|
-
|
|
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
|
-
|
|
10
|
-
|
|
9
|
+
leading?: boolean;
|
|
10
|
+
trailing?: boolean;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export function useDebouncedCallback({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
onChange,
|
|
15
|
+
timeThreshold,
|
|
16
|
+
leading = false,
|
|
17
|
+
trailing = true,
|
|
18
18
|
}: DebounceOptions & {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
onChange: (newValue: boolean) => void;
|
|
20
|
+
timeThreshold: number;
|
|
21
21
|
}) {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const handleChange = usePreservedCallback(onChange);
|
|
23
|
+
const ref = useRef({ value: false, clearPreviousDebounce: () => {} });
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
useEffect(function clearDebouncedOnUnmount() {
|
|
26
|
+
const current = ref.current;
|
|
27
|
+
return () => {
|
|
28
|
+
current.clearPreviousDebounce();
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const edges = useMemo(() => {
|
|
33
|
+
const _edges: Array<"leading" | "trailing"> = [];
|
|
34
|
+
if (leading) {
|
|
35
|
+
_edges.push("leading");
|
|
36
|
+
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (trailing) {
|
|
39
|
+
_edges.push("trailing");
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
return _edges;
|
|
43
|
+
}, [leading, trailing]);
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
return useCallback(
|
|
46
|
+
(nextValue: boolean) => {
|
|
47
|
+
if (nextValue === ref.current.value) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const debounced = debounce(
|
|
52
|
+
() => {
|
|
53
|
+
handleChange(nextValue);
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
ref.current.value = nextValue;
|
|
56
|
+
},
|
|
57
|
+
timeThreshold,
|
|
58
|
+
{ edges },
|
|
59
|
+
);
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
ref.current.clearPreviousDebounce();
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
debounced();
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
delay = 250,
|
|
13
|
+
click,
|
|
14
|
+
doubleClick,
|
|
15
15
|
}: UseDoubleClickProps<E>) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
}
|