import React, { useRef, MouseEvent, ReactNode, useState, RefObject, useContext, useEffect } from "react"; import { keyStateProvider } from "../../../helpers/KeyStateProvider"; import { Observable, Subject, Subscription } from "rxjs"; import { clearTextSelection } from "../../../helpers/tshelper"; export interface DragArgs { parentRect: DOMRect; rect: DOMRect } export const DragBoxContext = React.createContext<Observable<DragArgs> | undefined>(undefined); function checkIsIntersecting(parent: DOMRect, child: DOMRect) { return parent.left <= child.right && parent.right >= child.left && parent.top <= child.bottom && parent.bottom >= child.top; } export function useDragSelect(elementRef: RefObject<HTMLElement>, disabled?: boolean) { const dragObservable = useContext(DragBoxContext); const [isSelected, setIsSelected] = useState<boolean>(false); useEffect(() => { if (disabled) return; if (!dragObservable) return; const subscription: Subscription = dragObservable.subscribe((args: DragArgs) => { const e = elementRef.current; if (!e) return; if (keyStateProvider.keyStates.shift && isSelected) return; const bRect = e.getBoundingClientRect(); const pRect = args.parentRect; const rect2 = new DOMRect(bRect.x - pRect.x, bRect.y - pRect.y, bRect.width, bRect.height); const isIntersecting = checkIsIntersecting(args.rect, rect2); setIsSelected(isIntersecting); }); return () => subscription.unsubscribe(); }, [dragObservable, elementRef, isSelected, disabled]); return isSelected && !disabled; } export function DragBox(props: { children: ReactNode }) { const { children } = props; const [subject] = useState(new Subject<DragArgs>()) const containerRef = useRef<HTMLDivElement>(null); const draggingRef = useRef(false); const startX = useRef(0); const startY = useRef(0); function getDragRect(e: MouseEvent) { if (draggingRef.current && containerRef.current) { const rect = containerRef.current.getBoundingClientRect(); const width = Math.abs(e.clientX - startX.current); const height = Math.abs(e.clientY - startY.current); const left = Math.min(startX.current, e.clientX) - rect.left; const top = Math.min(startY.current, e.clientY) - rect.top; const dragRect = new DOMRect(left, top, width, height); return dragRect; } return undefined; } const onMouseDown = (e: MouseEvent<HTMLDivElement>) => { if (keyStateProvider.keyStates.control) return; e.preventDefault(); clearTextSelection(); draggingRef.current = true; startX.current = e.clientX; startY.current = e.clientY; window.addEventListener("mousemove", onMouseMove as any); window.addEventListener("mouseup", onMouseUp as any); }; const onMouseMove = (e: MouseEvent) => { const dragRect = getDragRect(e); if (!dragRect) return; drawDragBox(dragRect); }; const onMouseUp = (e: MouseEvent) => { const dragRect = getDragRect(e); if (!dragRect) return; subject.next({ parentRect: containerRef.current!.getBoundingClientRect(), rect: dragRect }) draggingRef.current = false; window.removeEventListener("mousemove", onMouseMove as any); window.removeEventListener("mouseup", onMouseUp as any); const dragBox = containerRef.current?.querySelector(".drag-box") as HTMLElement; if (!dragBox) return; dragBox.style.display = "none"; }; const drawDragBox = (rect: DOMRect) => { const dragBox = containerRef.current?.querySelector(".drag-box") as HTMLElement; if (!dragBox) return; dragBox.style.left = rect.x + "px"; dragBox.style.top = rect.y + "px"; dragBox.style.width = rect.width + "px"; dragBox.style.height = rect.height + "px"; dragBox.style.display = "block"; }; return ( <div ref={containerRef} style={{ position: "relative", width: "100%", height: "100%" }}> <div className="drag-box" style={{ display: "none", position: "absolute", border: "1.5px dotted red", background: "rgba(255,0,0,0.15)", borderRadius: 3, zIndex: 999 }} /> <div onMouseDown={onMouseDown} style={{ width: "100%", height: "100%" }}> <DragBoxContext.Provider value={subject}> {children} </DragBoxContext.Provider> </div> </div> ); };
<!-- Example --> <DragBox> <SelectableItem /> </DragBox> export function SelectableItem(props: { }) { const elementRef = useRef<HTMLDivElement>(null); const isSelected = useDragSelect(elementRef, readOnly); return ( <div ref={elementRef} className={joinClasses(isSelected ? styles.selected : undefined)} onMouseDown={(e) => e.stopPropagation()}> <!-- content --> </div> ); } export const joinClasses = (...args: any[]) => Array.from(args).filter(x => !!x).join(" "); export function clearTextSelection() { const w = window as any; const d = document as any; if (w.getSelection) { if (w.getSelection().empty) { // Chrome w.getSelection().empty(); } else if (w.getSelection().removeAllRanges) { // Firefox w.getSelection().removeAllRanges(); } } else if (d.selection) { // IE? d.selection.empty(); } }
833700cookie-checkReact DragBox