mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
refactor double click handler to add quicker single click
This commit is contained in:
@@ -1,45 +1,102 @@
|
|||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export const useDoubleClick = ({
|
export const useDoubleClick = ({
|
||||||
latency = 180,
|
doubleClickLatency = 300,
|
||||||
onDoubleClick = () => null,
|
onDoubleClick = () => null,
|
||||||
onSingleClick = () => null,
|
onSingleClick = () => null,
|
||||||
|
singleClickLatency = 20,
|
||||||
}: {
|
}: {
|
||||||
latency?: number;
|
doubleClickLatency?: number;
|
||||||
onDoubleClick?: (e: any) => void;
|
onDoubleClick?: (e: any) => void;
|
||||||
onSingleClick?: (e: any) => void;
|
onSingleClick?: (e: any) => void;
|
||||||
|
singleClickLatency?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const clickCountRef = useRef(0);
|
const clickCountRef = useRef(0);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const singleClickTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const doubleClickTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const singleClickFiredRef = useRef(false);
|
||||||
|
const lastClickEventRef = useRef<any>(null);
|
||||||
|
|
||||||
|
// Use latency for backward compatibility, but prefer doubleClickLatency
|
||||||
|
const effectiveDoubleClickLatency = doubleClickLatency;
|
||||||
|
const effectiveSingleClickLatency = singleClickLatency ?? 50;
|
||||||
|
|
||||||
|
const clearSingleClick = useCallback(() => {
|
||||||
|
if (singleClickTimeoutRef.current) {
|
||||||
|
clearTimeout(singleClickTimeoutRef.current);
|
||||||
|
singleClickTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
clickCountRef.current += 1;
|
clickCountRef.current += 1;
|
||||||
|
lastClickEventRef.current = e;
|
||||||
|
|
||||||
// Clear any existing timeout
|
if (clickCountRef.current === 1) {
|
||||||
if (timeoutRef.current) {
|
// First click: fire single click optimistically after short delay
|
||||||
clearTimeout(timeoutRef.current);
|
singleClickFiredRef.current = false;
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new timeout to determine if it's a single or double click
|
// Set double-click detection window first
|
||||||
timeoutRef.current = setTimeout(() => {
|
doubleClickTimeoutRef.current = setTimeout(() => {
|
||||||
if (clickCountRef.current === 1) {
|
clickCountRef.current = 0;
|
||||||
onSingleClick(e);
|
singleClickFiredRef.current = false;
|
||||||
} else if (clickCountRef.current === 2) {
|
}, effectiveDoubleClickLatency);
|
||||||
onDoubleClick(e);
|
|
||||||
|
// Fire single click after delay (defaults to 0 for immediate response)
|
||||||
|
if (effectiveSingleClickLatency > 0) {
|
||||||
|
singleClickTimeoutRef.current = setTimeout(() => {
|
||||||
|
// Only fire if still a single click and double click hasn't been detected
|
||||||
|
if (clickCountRef.current === 1 && !singleClickFiredRef.current) {
|
||||||
|
singleClickFiredRef.current = true;
|
||||||
|
onSingleClick(lastClickEventRef.current);
|
||||||
|
}
|
||||||
|
}, effectiveSingleClickLatency);
|
||||||
|
} else {
|
||||||
|
// Fire immediately if latency is 0
|
||||||
|
// Note: If double click comes immediately after, both may fire
|
||||||
|
// For best UX, use a small delay (e.g., 50ms) instead of 0
|
||||||
|
singleClickFiredRef.current = true;
|
||||||
|
onSingleClick(lastClickEventRef.current);
|
||||||
|
}
|
||||||
|
} else if (clickCountRef.current === 2) {
|
||||||
|
// Second click detected within double-click latency
|
||||||
|
// Cancel single click if it hasn't fired yet
|
||||||
|
if (!singleClickFiredRef.current) {
|
||||||
|
clearSingleClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire double click
|
||||||
|
onDoubleClick(e);
|
||||||
|
|
||||||
|
// Reset state
|
||||||
clickCountRef.current = 0;
|
clickCountRef.current = 0;
|
||||||
}, latency);
|
singleClickFiredRef.current = false;
|
||||||
|
|
||||||
|
// Clear double-click timeout
|
||||||
|
if (doubleClickTimeoutRef.current) {
|
||||||
|
clearTimeout(doubleClickTimeoutRef.current);
|
||||||
|
doubleClickTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[latency, onDoubleClick, onSingleClick],
|
[
|
||||||
|
effectiveDoubleClickLatency,
|
||||||
|
effectiveSingleClickLatency,
|
||||||
|
onDoubleClick,
|
||||||
|
onSingleClick,
|
||||||
|
clearSingleClick,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cleanup timeout on unmount
|
// Cleanup timeouts on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (timeoutRef.current) {
|
if (singleClickTimeoutRef.current) {
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(singleClickTimeoutRef.current);
|
||||||
|
}
|
||||||
|
if (doubleClickTimeoutRef.current) {
|
||||||
|
clearTimeout(doubleClickTimeoutRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
Reference in New Issue
Block a user