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