bafd9cbe75
- useFileDrop: reset drag overlay when mouse leaves browser window (relatedTarget === null signals viewport exit, counter was getting stuck) - useDeviceVerificationStatus: add member count to useMemo deps so new room members' devices get checked, not just initial joined members - index.css: define --bg-surface-variant used by VoiceMessageRecorder, MessageSearch, SearchFilters, UserRoomProfile (was falling back to transparent) - syntaxHighlight: fix Python inline comments — # after space/tab was treated as plain text; only start-of-line was recognised - usePresenceUpdater: replace internal baseUrl cast with mx.getHomeserverUrl() - useLocalMessageSearch: scan all linked timelines via getUnfilteredTimelineSet() not just the live window, so scrolled-back history is included in E2EE search - RoomViewHeader: show search button in encrypted rooms — local search is implemented and handles them; the guard was a holdover from before it existed - recent-emoji: return emojis in recency order (array is already unshifted on use) instead of sorting by total usage count Skipped: media gallery memory leak (needs virtualization refactor), bookmark race condition (needs queue/lock), Night Light portal coverage (position:fixed already covers full viewport — not a real bug). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { useCallback, DragEventHandler, RefObject, useState, useEffect, useRef } from 'react';
|
|
import { getDataTransferFiles } from '../utils/dom';
|
|
|
|
export const useFileDropHandler = (onDrop: (file: File[]) => void): DragEventHandler =>
|
|
useCallback(
|
|
(evt) => {
|
|
const files = getDataTransferFiles(evt.dataTransfer);
|
|
if (files) onDrop(files);
|
|
},
|
|
[onDrop],
|
|
);
|
|
|
|
export const useFileDropZone = (
|
|
zoneRef: RefObject<HTMLElement>,
|
|
onDrop: (file: File[]) => void,
|
|
): boolean => {
|
|
const dragCounterRef = useRef(0);
|
|
const [active, setActive] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const target = zoneRef.current;
|
|
const handleDrop = (evt: DragEvent) => {
|
|
evt.preventDefault();
|
|
dragCounterRef.current = 0;
|
|
setActive(false);
|
|
if (!evt.dataTransfer) return;
|
|
const files = getDataTransferFiles(evt.dataTransfer);
|
|
if (files) onDrop(files);
|
|
};
|
|
|
|
target?.addEventListener('drop', handleDrop);
|
|
return () => {
|
|
target?.removeEventListener('drop', handleDrop);
|
|
};
|
|
}, [zoneRef, onDrop]);
|
|
|
|
useEffect(() => {
|
|
const target = zoneRef.current;
|
|
const handleDragEnter = (evt: DragEvent) => {
|
|
if (evt.dataTransfer?.types.includes('Files')) {
|
|
dragCounterRef.current += 1;
|
|
setActive(true);
|
|
}
|
|
};
|
|
const handleDragLeave = (evt: DragEvent) => {
|
|
if (evt.relatedTarget === null) {
|
|
// Mouse left the browser window — reset unconditionally
|
|
dragCounterRef.current = 0;
|
|
setActive(false);
|
|
return;
|
|
}
|
|
dragCounterRef.current -= 1;
|
|
if (dragCounterRef.current <= 0) {
|
|
dragCounterRef.current = 0;
|
|
setActive(false);
|
|
}
|
|
};
|
|
const handleDragOver = (evt: DragEvent) => {
|
|
evt.preventDefault();
|
|
};
|
|
|
|
target?.addEventListener('dragenter', handleDragEnter);
|
|
target?.addEventListener('dragleave', handleDragLeave);
|
|
target?.addEventListener('dragover', handleDragOver);
|
|
return () => {
|
|
target?.removeEventListener('dragenter', handleDragEnter);
|
|
target?.removeEventListener('dragleave', handleDragLeave);
|
|
target?.removeEventListener('dragover', handleDragOver);
|
|
};
|
|
}, [zoneRef]);
|
|
|
|
return active;
|
|
};
|