preact-missing-hooks 3.0.0 → 4.0.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 (79) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.husky/pre-push +1 -0
  3. package/.prettierignore +3 -0
  4. package/.prettierrc +6 -0
  5. package/Readme.md +179 -131
  6. package/dist/entry.cjs +21 -0
  7. package/dist/entry.js +2 -0
  8. package/dist/entry.js.map +1 -0
  9. package/dist/entry.modern.mjs +2 -0
  10. package/dist/entry.modern.mjs.map +1 -0
  11. package/dist/entry.module.js +2 -0
  12. package/dist/entry.module.js.map +1 -0
  13. package/dist/entry.umd.js +2 -0
  14. package/dist/entry.umd.js.map +1 -0
  15. package/dist/index.d.ts +13 -12
  16. package/dist/index.js +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.modern.mjs +2 -0
  19. package/dist/index.modern.mjs.map +1 -0
  20. package/dist/index.module.js +1 -1
  21. package/dist/index.module.js.map +1 -1
  22. package/dist/index.umd.js +1 -1
  23. package/dist/index.umd.js.map +1 -1
  24. package/dist/indexedDB/dbController.d.ts +2 -2
  25. package/dist/indexedDB/index.d.ts +6 -6
  26. package/dist/indexedDB/openDB.d.ts +1 -1
  27. package/dist/indexedDB/tableController.d.ts +1 -1
  28. package/dist/indexedDB/types.d.ts +1 -2
  29. package/dist/react.js +1 -0
  30. package/dist/react.modern.mjs +1 -0
  31. package/dist/react.module.js +1 -0
  32. package/dist/react.umd.js +1 -0
  33. package/dist/useEventBus.d.ts +1 -1
  34. package/dist/useIndexedDB.d.ts +3 -3
  35. package/dist/useMutationObserver.d.ts +1 -1
  36. package/dist/useNetworkState.d.ts +3 -3
  37. package/dist/usePreferredTheme.d.ts +1 -1
  38. package/dist/useRageClick.d.ts +1 -1
  39. package/dist/useThreadedWorker.d.ts +1 -1
  40. package/dist/useTransition.d.ts +4 -1
  41. package/dist/useWorkerNotifications.d.ts +57 -0
  42. package/dist/useWrappedChildren.d.ts +3 -3
  43. package/{demo → docs}/index.html +56 -0
  44. package/{demo → docs}/main.js +437 -312
  45. package/eslint.config.mjs +10 -0
  46. package/package.json +65 -6
  47. package/scripts/generate-entry.cjs +34 -0
  48. package/src/index.ts +13 -12
  49. package/src/indexedDB/dbController.ts +101 -92
  50. package/src/indexedDB/index.ts +16 -11
  51. package/src/indexedDB/openDB.ts +49 -49
  52. package/src/indexedDB/requestToPromise.ts +17 -16
  53. package/src/indexedDB/tableController.ts +331 -257
  54. package/src/indexedDB/types.ts +35 -35
  55. package/src/useClipboard.ts +99 -97
  56. package/src/useEventBus.ts +39 -36
  57. package/src/useIndexedDB.ts +111 -111
  58. package/src/useMutationObserver.ts +26 -26
  59. package/src/useNetworkState.ts +124 -122
  60. package/src/usePreferredTheme.ts +68 -68
  61. package/src/useRageClick.ts +103 -103
  62. package/src/useThreadedWorker.ts +165 -165
  63. package/src/useTransition.ts +22 -19
  64. package/src/useWasmCompute.ts +209 -204
  65. package/src/useWebRTCIP.ts +181 -176
  66. package/src/useWorkerNotifications.ts +203 -0
  67. package/src/useWrappedChildren.ts +72 -58
  68. package/tests/react-adapter.tsx +12 -0
  69. package/tests/setup-react.ts +4 -0
  70. package/tests/useClipboard.test.tsx +4 -2
  71. package/tests/useThreadedWorker.test.tsx +3 -1
  72. package/tests/useWasmCompute.test.tsx +1 -1
  73. package/tests/useWebRTCIP.test.tsx +3 -1
  74. package/tests/useWorkerNotifications.test.tsx +170 -0
  75. package/vite.config.ts +11 -4
  76. package/vitest.config.preact.ts +20 -0
  77. package/vitest.config.react.ts +36 -0
  78. package/vitest.workspace.ts +6 -0
  79. /package/{demo → docs}/add.wasm +0 -0
@@ -1,122 +1,124 @@
1
- import { useEffect, useState } from 'preact/hooks';
2
-
3
- /** Network Information API (not in all browsers) */
4
- interface NetworkInformation extends EventTarget {
5
- effectiveType?: string;
6
- downlink?: number;
7
- rtt?: number;
8
- saveData?: boolean;
9
- type?: string;
10
- }
11
-
12
- /** Effective connection type from Network Information API */
13
- export type EffectiveConnectionType = 'slow-2g' | '2g' | '3g' | '4g';
14
-
15
- /** Network connection type (e.g., wifi, cellular) */
16
- export type ConnectionType =
17
- | 'bluetooth'
18
- | 'cellular'
19
- | 'ethernet'
20
- | 'mixed'
21
- | 'none'
22
- | 'other'
23
- | 'unknown'
24
- | 'wifi';
25
-
26
- export interface NetworkState {
27
- /** Whether the browser is online */
28
- online: boolean;
29
- /** Effective connection type (when supported) */
30
- effectiveType?: EffectiveConnectionType;
31
- /** Estimated downlink speed in Mbps (when supported) */
32
- downlink?: number;
33
- /** Estimated round-trip time in ms (when supported) */
34
- rtt?: number;
35
- /** Whether the user has requested reduced data usage (when supported) */
36
- saveData?: boolean;
37
- /** Connection type (when supported) */
38
- connectionType?: ConnectionType;
39
- }
40
-
41
- function getNetworkState(): NetworkState {
42
- if (typeof navigator === 'undefined') {
43
- return { online: true };
44
- }
45
-
46
- const state: NetworkState = {
47
- online: navigator.onLine,
48
- };
49
-
50
- const connection =
51
- (navigator as Navigator & { connection?: NetworkInformation }).connection;
52
-
53
- if (connection) {
54
- if (connection.effectiveType !== undefined) {
55
- state.effectiveType = connection.effectiveType as EffectiveConnectionType;
56
- }
57
- if (connection.downlink !== undefined) {
58
- state.downlink = connection.downlink;
59
- }
60
- if (connection.rtt !== undefined) {
61
- state.rtt = connection.rtt;
62
- }
63
- if (connection.saveData !== undefined) {
64
- state.saveData = connection.saveData;
65
- }
66
- if (connection.type !== undefined) {
67
- state.connectionType = connection.type as ConnectionType;
68
- }
69
- }
70
-
71
- return state;
72
- }
73
-
74
- /**
75
- * A Preact hook that returns the current network state, including online/offline
76
- * status and (when supported) connection type, downlink, RTT, and save-data preference.
77
- * Updates reactively when the network state changes.
78
- *
79
- * @returns The current network state object
80
- *
81
- * @example
82
- * ```tsx
83
- * function NetworkStatus() {
84
- * const { online, effectiveType, saveData } = useNetworkState();
85
- * return (
86
- * <div>
87
- * Status: {online ? 'Online' : 'Offline'}
88
- * {effectiveType && ` (${effectiveType})`}
89
- * {saveData && ' - Reduced data mode'}
90
- * </div>
91
- * );
92
- * }
93
- * ```
94
- */
95
- export function useNetworkState(): NetworkState {
96
- const [state, setState] = useState<NetworkState>(getNetworkState);
97
-
98
- useEffect(() => {
99
- if (typeof window === 'undefined') return;
100
-
101
- const updateState = () => setState(getNetworkState());
102
-
103
- window.addEventListener('online', updateState);
104
- window.addEventListener('offline', updateState);
105
-
106
- const connection = (navigator as Navigator & { connection?: NetworkInformation })
107
- .connection;
108
- if (connection?.addEventListener) {
109
- connection.addEventListener('change', updateState);
110
- }
111
-
112
- return () => {
113
- window.removeEventListener('online', updateState);
114
- window.removeEventListener('offline', updateState);
115
- if (connection?.removeEventListener) {
116
- connection.removeEventListener('change', updateState);
117
- }
118
- };
119
- }, []);
120
-
121
- return state;
122
- }
1
+ import { useEffect, useState } from "preact/hooks";
2
+
3
+ /** Network Information API (not in all browsers) */
4
+ interface NetworkInformation extends EventTarget {
5
+ effectiveType?: string;
6
+ downlink?: number;
7
+ rtt?: number;
8
+ saveData?: boolean;
9
+ type?: string;
10
+ }
11
+
12
+ /** Effective connection type from Network Information API */
13
+ export type EffectiveConnectionType = "slow-2g" | "2g" | "3g" | "4g";
14
+
15
+ /** Network connection type (e.g., wifi, cellular) */
16
+ export type ConnectionType =
17
+ | "bluetooth"
18
+ | "cellular"
19
+ | "ethernet"
20
+ | "mixed"
21
+ | "none"
22
+ | "other"
23
+ | "unknown"
24
+ | "wifi";
25
+
26
+ export interface NetworkState {
27
+ /** Whether the browser is online */
28
+ online: boolean;
29
+ /** Effective connection type (when supported) */
30
+ effectiveType?: EffectiveConnectionType;
31
+ /** Estimated downlink speed in Mbps (when supported) */
32
+ downlink?: number;
33
+ /** Estimated round-trip time in ms (when supported) */
34
+ rtt?: number;
35
+ /** Whether the user has requested reduced data usage (when supported) */
36
+ saveData?: boolean;
37
+ /** Connection type (when supported) */
38
+ connectionType?: ConnectionType;
39
+ }
40
+
41
+ function getNetworkState(): NetworkState {
42
+ if (typeof navigator === "undefined") {
43
+ return { online: true };
44
+ }
45
+
46
+ const state: NetworkState = {
47
+ online: navigator.onLine,
48
+ };
49
+
50
+ const connection = (
51
+ navigator as Navigator & { connection?: NetworkInformation }
52
+ ).connection;
53
+
54
+ if (connection) {
55
+ if (connection.effectiveType !== undefined) {
56
+ state.effectiveType = connection.effectiveType as EffectiveConnectionType;
57
+ }
58
+ if (connection.downlink !== undefined) {
59
+ state.downlink = connection.downlink;
60
+ }
61
+ if (connection.rtt !== undefined) {
62
+ state.rtt = connection.rtt;
63
+ }
64
+ if (connection.saveData !== undefined) {
65
+ state.saveData = connection.saveData;
66
+ }
67
+ if (connection.type !== undefined) {
68
+ state.connectionType = connection.type as ConnectionType;
69
+ }
70
+ }
71
+
72
+ return state;
73
+ }
74
+
75
+ /**
76
+ * A Preact hook that returns the current network state, including online/offline
77
+ * status and (when supported) connection type, downlink, RTT, and save-data preference.
78
+ * Updates reactively when the network state changes.
79
+ *
80
+ * @returns The current network state object
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * function NetworkStatus() {
85
+ * const { online, effectiveType, saveData } = useNetworkState();
86
+ * return (
87
+ * <div>
88
+ * Status: {online ? 'Online' : 'Offline'}
89
+ * {effectiveType && ` (${effectiveType})`}
90
+ * {saveData && ' - Reduced data mode'}
91
+ * </div>
92
+ * );
93
+ * }
94
+ * ```
95
+ */
96
+ export function useNetworkState(): NetworkState {
97
+ const [state, setState] = useState<NetworkState>(getNetworkState);
98
+
99
+ useEffect(() => {
100
+ if (typeof window === "undefined") return;
101
+
102
+ const updateState = () => setState(getNetworkState());
103
+
104
+ window.addEventListener("online", updateState);
105
+ window.addEventListener("offline", updateState);
106
+
107
+ const connection = (
108
+ navigator as Navigator & { connection?: NetworkInformation }
109
+ ).connection;
110
+ if (connection?.addEventListener) {
111
+ connection.addEventListener("change", updateState);
112
+ }
113
+
114
+ return () => {
115
+ window.removeEventListener("online", updateState);
116
+ window.removeEventListener("offline", updateState);
117
+ if (connection?.removeEventListener) {
118
+ connection.removeEventListener("change", updateState);
119
+ }
120
+ };
121
+ }, []);
122
+
123
+ return state;
124
+ }
@@ -1,68 +1,68 @@
1
- import { useEffect, useState } from 'preact/hooks';
2
-
3
- export type PreferredTheme = 'light' | 'dark' | 'no-preference';
4
-
5
- /**
6
- * A Preact hook that returns the user's preferred color scheme based on the
7
- * `prefers-color-scheme` media query. Updates reactively when the user changes
8
- * their system or browser theme preference.
9
- *
10
- * @returns The preferred theme: 'light', 'dark', or 'no-preference'
11
- *
12
- * @example
13
- * ```tsx
14
- * function ThemeAwareComponent() {
15
- * const theme = usePreferredTheme();
16
- * return (
17
- * <div data-theme={theme}>
18
- * Current preference: {theme}
19
- * </div>
20
- * );
21
- * }
22
- * ```
23
- */
24
- export function usePreferredTheme(): PreferredTheme {
25
- const [theme, setTheme] = useState<PreferredTheme>(() => {
26
- if (typeof window === 'undefined') return 'no-preference';
27
-
28
- const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
29
- const lightQuery = window.matchMedia('(prefers-color-scheme: light)');
30
-
31
- if (darkQuery.matches) return 'dark';
32
- if (lightQuery.matches) return 'light';
33
- return 'no-preference';
34
- });
35
-
36
- useEffect(() => {
37
- if (typeof window === 'undefined') return;
38
-
39
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
40
-
41
- const handleChange = (e: MediaQueryListEvent) => {
42
- setTheme(e.matches ? 'dark' : 'light');
43
- };
44
-
45
- // Re-check in case of no-preference (some browsers don't support light query)
46
- const updateTheme = () => {
47
- const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
48
- const lightQuery = window.matchMedia('(prefers-color-scheme: light)');
49
-
50
- if (darkQuery.matches) setTheme('dark');
51
- else if (lightQuery.matches) setTheme('light');
52
- else setTheme('no-preference');
53
- };
54
-
55
- mediaQuery.addEventListener('change', handleChange);
56
-
57
- // Fallback: some environments may not fire change, so we also listen for light
58
- const lightQuery = window.matchMedia('(prefers-color-scheme: light)');
59
- lightQuery.addEventListener('change', updateTheme);
60
-
61
- return () => {
62
- mediaQuery.removeEventListener('change', handleChange);
63
- lightQuery.removeEventListener('change', updateTheme);
64
- };
65
- }, []);
66
-
67
- return theme;
68
- }
1
+ import { useEffect, useState } from "preact/hooks";
2
+
3
+ export type PreferredTheme = "light" | "dark" | "no-preference";
4
+
5
+ /**
6
+ * A Preact hook that returns the user's preferred color scheme based on the
7
+ * `prefers-color-scheme` media query. Updates reactively when the user changes
8
+ * their system or browser theme preference.
9
+ *
10
+ * @returns The preferred theme: 'light', 'dark', or 'no-preference'
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * function ThemeAwareComponent() {
15
+ * const theme = usePreferredTheme();
16
+ * return (
17
+ * <div data-theme={theme}>
18
+ * Current preference: {theme}
19
+ * </div>
20
+ * );
21
+ * }
22
+ * ```
23
+ */
24
+ export function usePreferredTheme(): PreferredTheme {
25
+ const [theme, setTheme] = useState<PreferredTheme>(() => {
26
+ if (typeof window === "undefined") return "no-preference";
27
+
28
+ const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
29
+ const lightQuery = window.matchMedia("(prefers-color-scheme: light)");
30
+
31
+ if (darkQuery.matches) return "dark";
32
+ if (lightQuery.matches) return "light";
33
+ return "no-preference";
34
+ });
35
+
36
+ useEffect(() => {
37
+ if (typeof window === "undefined") return;
38
+
39
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
40
+
41
+ const handleChange = (e: MediaQueryListEvent) => {
42
+ setTheme(e.matches ? "dark" : "light");
43
+ };
44
+
45
+ // Re-check in case of no-preference (some browsers don't support light query)
46
+ const updateTheme = () => {
47
+ const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
48
+ const lightQuery = window.matchMedia("(prefers-color-scheme: light)");
49
+
50
+ if (darkQuery.matches) setTheme("dark");
51
+ else if (lightQuery.matches) setTheme("light");
52
+ else setTheme("no-preference");
53
+ };
54
+
55
+ mediaQuery.addEventListener("change", handleChange);
56
+
57
+ // Fallback: some environments may not fire change, so we also listen for light
58
+ const lightQuery = window.matchMedia("(prefers-color-scheme: light)");
59
+ lightQuery.addEventListener("change", updateTheme);
60
+
61
+ return () => {
62
+ mediaQuery.removeEventListener("change", handleChange);
63
+ lightQuery.removeEventListener("change", updateTheme);
64
+ };
65
+ }, []);
66
+
67
+ return theme;
68
+ }
@@ -1,103 +1,103 @@
1
- import type { RefObject } from 'preact'
2
- import { useEffect, useRef } from 'preact/hooks'
3
-
4
- export interface RageClickPayload {
5
- /** Number of clicks that triggered the rage click */
6
- count: number
7
- /** Last click event (e.g. for Sentry context) */
8
- event: MouseEvent
9
- }
10
-
11
- export interface UseRageClickOptions {
12
- /** Called when a rage click is detected. Use this to report to Sentry or your error tracker. */
13
- onRageClick: (payload: RageClickPayload) => void
14
- /** Minimum number of clicks in the time window to count as rage click. Default: 5 (Sentry-style). */
15
- threshold?: number
16
- /** Time window in ms. Default: 1000. */
17
- timeWindow?: number
18
- /** Max distance in px between clicks to count as same spot. Default: 30. Set to Infinity to ignore distance. */
19
- distanceThreshold?: number
20
- }
21
-
22
- interface ClickRecord {
23
- time: number
24
- x: number
25
- y: number
26
- }
27
-
28
- function distance(a: ClickRecord, b: ClickRecord): number {
29
- return Math.hypot(b.x - a.x, b.y - a.y)
30
- }
31
-
32
- /**
33
- * Detects "rage clicks" (repeated rapid clicks in the same area), e.g. when the UI
34
- * is unresponsive. Use the callback to report to Sentry or similar tools to surface
35
- * rage click issues and lower rage-click-related support.
36
- *
37
- * @param targetRef - Ref of the element to monitor (e.g. a button or card).
38
- * @param options - onRageClick callback and optional threshold, timeWindow, distanceThreshold.
39
- *
40
- * @example
41
- * ```tsx
42
- * const ref = useRef<HTMLButtonElement>(null)
43
- * useRageClick(ref, {
44
- * onRageClick: ({ count, event }) => {
45
- * Sentry.captureMessage('Rage click detected', { extra: { count, target: event.target } })
46
- * },
47
- * })
48
- * return <button ref={ref}>Submit</button>
49
- * ```
50
- */
51
- export function useRageClick(
52
- targetRef: RefObject<HTMLElement | null>,
53
- options: UseRageClickOptions
54
- ) {
55
- const {
56
- onRageClick,
57
- threshold = 5,
58
- timeWindow = 1000,
59
- distanceThreshold = 30,
60
- } = options
61
-
62
- const onRageClickRef = useRef(onRageClick)
63
- onRageClickRef.current = onRageClick
64
-
65
- const clicksRef = useRef<ClickRecord[]>([])
66
-
67
- useEffect(() => {
68
- const node = targetRef.current
69
- if (!node) return
70
-
71
- const handleClick = (e: MouseEvent) => {
72
- const now = Date.now()
73
- const record: ClickRecord = { time: now, x: e.clientX, y: e.clientY }
74
-
75
- const clicks = clicksRef.current
76
- const cutoff = now - timeWindow
77
- const recent = clicks.filter((c) => c.time >= cutoff)
78
- recent.push(record)
79
-
80
- if (distanceThreshold !== Infinity) {
81
- const inRange = recent.filter(
82
- (c) => distance(c, record) <= distanceThreshold
83
- )
84
- if (inRange.length >= threshold) {
85
- onRageClickRef.current({ count: inRange.length, event: e })
86
- clicksRef.current = []
87
- return
88
- }
89
- } else {
90
- if (recent.length >= threshold) {
91
- onRageClickRef.current({ count: recent.length, event: e })
92
- clicksRef.current = []
93
- return
94
- }
95
- }
96
-
97
- clicksRef.current = recent
98
- }
99
-
100
- node.addEventListener('click', handleClick)
101
- return () => node.removeEventListener('click', handleClick)
102
- }, [targetRef, threshold, timeWindow, distanceThreshold])
103
- }
1
+ import type { RefObject } from "preact";
2
+ import { useEffect, useRef } from "preact/hooks";
3
+
4
+ export interface RageClickPayload {
5
+ /** Number of clicks that triggered the rage click */
6
+ count: number;
7
+ /** Last click event (e.g. for Sentry context) */
8
+ event: MouseEvent;
9
+ }
10
+
11
+ export interface UseRageClickOptions {
12
+ /** Called when a rage click is detected. Use this to report to Sentry or your error tracker. */
13
+ onRageClick: (payload: RageClickPayload) => void;
14
+ /** Minimum number of clicks in the time window to count as rage click. Default: 5 (Sentry-style). */
15
+ threshold?: number;
16
+ /** Time window in ms. Default: 1000. */
17
+ timeWindow?: number;
18
+ /** Max distance in px between clicks to count as same spot. Default: 30. Set to Infinity to ignore distance. */
19
+ distanceThreshold?: number;
20
+ }
21
+
22
+ interface ClickRecord {
23
+ time: number;
24
+ x: number;
25
+ y: number;
26
+ }
27
+
28
+ function distance(a: ClickRecord, b: ClickRecord): number {
29
+ return Math.hypot(b.x - a.x, b.y - a.y);
30
+ }
31
+
32
+ /**
33
+ * Detects "rage clicks" (repeated rapid clicks in the same area), e.g. when the UI
34
+ * is unresponsive. Use the callback to report to Sentry or similar tools to surface
35
+ * rage click issues and lower rage-click-related support.
36
+ *
37
+ * @param targetRef - Ref of the element to monitor (e.g. a button or card).
38
+ * @param options - onRageClick callback and optional threshold, timeWindow, distanceThreshold.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * const ref = useRef<HTMLButtonElement>(null)
43
+ * useRageClick(ref, {
44
+ * onRageClick: ({ count, event }) => {
45
+ * Sentry.captureMessage('Rage click detected', { extra: { count, target: event.target } })
46
+ * },
47
+ * })
48
+ * return <button ref={ref}>Submit</button>
49
+ * ```
50
+ */
51
+ export function useRageClick(
52
+ targetRef: RefObject<HTMLElement | null>,
53
+ options: UseRageClickOptions
54
+ ) {
55
+ const {
56
+ onRageClick,
57
+ threshold = 5,
58
+ timeWindow = 1000,
59
+ distanceThreshold = 30,
60
+ } = options;
61
+
62
+ const onRageClickRef = useRef(onRageClick);
63
+ onRageClickRef.current = onRageClick;
64
+
65
+ const clicksRef = useRef<ClickRecord[]>([]);
66
+
67
+ useEffect(() => {
68
+ const node = targetRef.current;
69
+ if (!node) return;
70
+
71
+ const handleClick = (e: MouseEvent) => {
72
+ const now = Date.now();
73
+ const record: ClickRecord = { time: now, x: e.clientX, y: e.clientY };
74
+
75
+ const clicks = clicksRef.current;
76
+ const cutoff = now - timeWindow;
77
+ const recent = clicks.filter((c) => c.time >= cutoff);
78
+ recent.push(record);
79
+
80
+ if (distanceThreshold !== Infinity) {
81
+ const inRange = recent.filter(
82
+ (c) => distance(c, record) <= distanceThreshold
83
+ );
84
+ if (inRange.length >= threshold) {
85
+ onRageClickRef.current({ count: inRange.length, event: e });
86
+ clicksRef.current = [];
87
+ return;
88
+ }
89
+ } else {
90
+ if (recent.length >= threshold) {
91
+ onRageClickRef.current({ count: recent.length, event: e });
92
+ clicksRef.current = [];
93
+ return;
94
+ }
95
+ }
96
+
97
+ clicksRef.current = recent;
98
+ };
99
+
100
+ node.addEventListener("click", handleClick);
101
+ return () => node.removeEventListener("click", handleClick);
102
+ }, [targetRef, threshold, timeWindow, distanceThreshold]);
103
+ }