Files
IdP/apps/frontend/hooks/use-chat-drag-selection.ts
2026-06-26 00:16:17 +03:00

95 lines
2.8 KiB
TypeScript

'use client';
import { useCallback, useEffect, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react';
const DRAG_THRESHOLD_PX = 6;
interface UseChatDragSelectionOptions {
selectionMode: boolean;
enterSelectionMode: (messageId: string) => void;
addSelectedMessage: (messageId: string) => void;
}
const dragSuppressedUntilRef = { current: 0 };
export function suppressChatDragSelection(durationMs = 400) {
dragSuppressedUntilRef.current = Date.now() + durationMs;
}
function isDragSuppressed() {
return Date.now() < dragSuppressedUntilRef.current;
}
export function useChatDragSelection({
selectionMode,
enterSelectionMode,
addSelectedMessage
}: UseChatDragSelectionOptions) {
const [isDragging, setIsDragging] = useState(false);
const visitedRef = useRef<Set<string>>(new Set());
const pendingDragRef = useRef<{ messageId: string; x: number; y: number } | null>(null);
const startDragSelection = useCallback(
(messageId: string) => {
if (isDragSuppressed()) return;
setIsDragging(true);
visitedRef.current = new Set([messageId]);
if (!selectionMode) {
enterSelectionMode(messageId);
} else {
addSelectedMessage(messageId);
}
},
[addSelectedMessage, enterSelectionMode, selectionMode]
);
const handleMessagePointerDown = useCallback(
(messageId: string, event: ReactMouseEvent) => {
if (event.button !== 0 || isDragSuppressed()) return;
pendingDragRef.current = { messageId, x: event.clientX, y: event.clientY };
},
[]
);
const handleDragEnterMessage = useCallback(
(messageId: string) => {
if (!isDragging || visitedRef.current.has(messageId)) return;
visitedRef.current.add(messageId);
addSelectedMessage(messageId);
},
[addSelectedMessage, isDragging]
);
const endDragSelection = useCallback(() => {
pendingDragRef.current = null;
setIsDragging(false);
visitedRef.current.clear();
}, []);
useEffect(() => {
const onMouseMove = (event: MouseEvent) => {
const pending = pendingDragRef.current;
if (!pending || isDragSuppressed()) return;
const distance = Math.hypot(event.clientX - pending.x, event.clientY - pending.y);
if (distance < DRAG_THRESHOLD_PX) return;
const messageId = pending.messageId;
pendingDragRef.current = null;
startDragSelection(messageId);
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', endDragSelection);
return () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', endDragSelection);
};
}, [endDragSelection, startDragSelection]);
return {
isDragging,
handleMessagePointerDown,
handleDragEnterMessage,
endDragSelection
};
}