@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.
- 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 +37 -0
- package/.agents/references/hooks/useInterval.md +40 -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 +33 -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/AGENTS.md +103 -396
- package/README.md +18 -5
- package/index.ts +63 -68
- package/package.json +27 -13
- package/packages/components/ScrolltoTop.tsx +3 -3
- package/packages/components/SwitchCase.tsx +7 -7
- package/packages/components/ViewportPortal.tsx +23 -23
- package/packages/cookie/cookie.shared.ts +97 -101
- package/packages/datetime/dateTime.client.ts +86 -86
- package/packages/datetime/dateTime.server.ts +86 -86
- package/packages/datetime/dateTime.shared.ts +281 -281
- 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 +33 -33
- package/packages/hooks/useDebounce.ts +95 -95
- package/packages/hooks/useDebouncedCallback.ts +45 -45
- package/packages/hooks/useDoubleClick.ts +43 -43
- package/packages/hooks/useGeolocation.ts +130 -130
- package/packages/hooks/useHasMounted.ts +5 -5
- package/packages/hooks/useIntersectionObserver.ts +20 -20
- package/packages/hooks/useInterval.ts +46 -45
- package/packages/hooks/useKeyboardHeight.ts +9 -9
- package/packages/hooks/useKeyboardListNavigation.ts +156 -156
- package/packages/hooks/useLongPress.ts +98 -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 +37 -37
- package/packages/hooks/useTimeout.ts +41 -41
- package/packages/hooks/useToggleState.ts +5 -5
- package/packages/hooks/useViewportHeight.ts +13 -13
- package/packages/hooks/useViewportMatch.ts +16 -22
- package/packages/utils/browserStorage.ts +26 -26
- package/packages/utils/buildContext.tsx +10 -10
- 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 -71
- 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 +46 -46
- package/tests/components/ScrolltoTop.test.tsx +15 -0
- package/tests/components/SwitchCase.test.tsx +34 -0
- package/tests/components/ViewportPortal.test.tsx +42 -0
- package/tests/cookie/cookie.test.ts +156 -0
- package/tests/datetime/datetime.test.ts +250 -0
- package/tests/hooks/useAvoidKeyboard.test.ts +12 -0
- package/tests/hooks/useCheckInvisible.test.ts +28 -0
- package/tests/hooks/useCheckScroll.test.ts +23 -0
- package/tests/hooks/useClientDateTime.test.ts +14 -0
- package/tests/hooks/useDebounce.test.ts +71 -0
- package/tests/hooks/useDebouncedCallback.test.ts +34 -0
- package/tests/hooks/useDoubleClick.test.ts +31 -0
- package/tests/hooks/useGeolocation.test.ts +50 -0
- package/tests/hooks/useHasMounted.test.ts +10 -0
- package/tests/hooks/useIntersectionObserver.test.ts +13 -0
- package/tests/hooks/useInterval.test.ts +31 -0
- package/tests/hooks/useKeyboardHeight.test.ts +10 -0
- package/tests/hooks/useKeyboardListNavigation.test.ts +82 -0
- package/tests/hooks/useLongPress.test.ts +52 -0
- package/tests/hooks/usePreservedCallback.test.ts +35 -0
- package/tests/hooks/usePreservedReference.test.ts +35 -0
- package/tests/hooks/useRefEffect.test.ts +30 -0
- package/tests/hooks/useRelativeDateTime.test.ts +19 -0
- package/tests/hooks/useTimeout.test.ts +34 -0
- package/tests/hooks/useToggleState.test.ts +35 -0
- package/tests/hooks/useViewportHeight.test.ts +10 -0
- package/tests/hooks/useViewportMatch.test.ts +35 -0
- package/tests/setup.ts +9 -0
- package/tests/utils/browserStorage.test.ts +54 -0
- package/tests/utils/buildContext.test.tsx +27 -0
- package/tests/utils/checkDevice.test.ts +84 -0
- package/tests/utils/clipboardShare.test.ts +78 -0
- package/tests/utils/floatingMotion.test.ts +84 -0
- package/tests/utils/keyboardTarget.test.ts +44 -0
- package/tests/utils/mergeRefs.test.ts +18 -0
- package/tests/utils/seen.test.ts +31 -0
- package/tests/utils/subscribeKeyboardHeight.test.ts +50 -0
- package/tsconfig.json +16 -16
- package/vitest.config.ts +16 -0
|
@@ -3,26 +3,26 @@
|
|
|
3
3
|
import { useKeyboardHeight } from "./useKeyboardHeight";
|
|
4
4
|
|
|
5
5
|
type UseAvoidKeyboardOptions = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
safeAreaBottom?: number;
|
|
7
|
+
transitionDuration?: number;
|
|
8
|
+
transitionTimingFunction?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export function useAvoidKeyboard(options?: UseAvoidKeyboardOptions) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
const {
|
|
13
|
+
safeAreaBottom = 0,
|
|
14
|
+
transitionDuration = 200,
|
|
15
|
+
transitionTimingFunction = "ease-out",
|
|
16
|
+
} = options ?? {};
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const { keyboardHeight } = useKeyboardHeight();
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const offset = keyboardHeight > 0 ? keyboardHeight : safeAreaBottom;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const style: React.CSSProperties = {
|
|
23
|
+
transform: offset > 0 ? `translateY(-${offset}px)` : undefined,
|
|
24
|
+
transition: `transform ${transitionDuration}ms ${transitionTimingFunction}`,
|
|
25
|
+
};
|
|
26
26
|
|
|
27
|
-
|
|
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
|
-
|
|
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(`.${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
|
}
|
|
@@ -3,47 +3,47 @@
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { formatClientDateTime } from "../datetime/dateTime.client";
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
locale?: string;
|
|
14
|
+
timeZone?: string;
|
|
15
|
+
datePreset?: DatePreset;
|
|
16
|
+
timePreset?: TimePreset;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export function useClientDateTime(
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
value: DateInput,
|
|
21
|
+
options?: UseClientDateTimeOptions,
|
|
22
22
|
) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const [ready, setReady] = useState(false);
|
|
24
|
+
const [text, setText] = useState("");
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
8
|
+
func: F,
|
|
9
|
+
debounceMs: number,
|
|
10
|
+
{
|
|
11
|
+
edges = ["leading", "trailing"],
|
|
12
|
+
}: { edges?: Array<"leading" | "trailing"> } = {},
|
|
13
13
|
): {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
(...args: Parameters<F>): void;
|
|
15
|
+
cancel: () => void;
|
|
16
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
78
|
+
leading?: boolean;
|
|
79
|
+
trailing?: boolean;
|
|
80
80
|
};
|
|
81
81
|
|
|
82
82
|
export function useDebounce<F extends (...args: unknown[]) => void>(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
callback: F,
|
|
84
|
+
wait: number,
|
|
85
|
+
options: DebounceOptions = {},
|
|
86
86
|
) {
|
|
87
|
-
|
|
87
|
+
const preservedCallback = usePreservedCallback(callback) as F;
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
const { leading = false, trailing = true } = options;
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const edges = useMemo(() => {
|
|
92
|
+
const _edges: Array<"leading" | "trailing"> = [];
|
|
93
|
+
if (leading) {
|
|
94
|
+
_edges.push("leading");
|
|
95
|
+
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
if (trailing) {
|
|
98
|
+
_edges.push("trailing");
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
return _edges;
|
|
102
|
+
}, [leading, trailing]);
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const debounced = useMemo(() => {
|
|
105
|
+
return debounce(preservedCallback, wait, { edges });
|
|
106
|
+
}, [preservedCallback, wait, edges]);
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
useEffect(
|
|
109
|
+
function cancelDebouncedOnUnmount() {
|
|
110
|
+
return () => {
|
|
111
|
+
debounced.cancel();
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
[debounced],
|
|
115
|
+
);
|
|
116
116
|
|
|
117
|
-
|
|
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
|
-
|
|
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
|
}
|