React: Drag Drop (Custom Implementation)

Date: 2021-11-22
// DragItem.tsx
import React, { DragEventHandler, useRef } from "react";

export interface IDragItem<T> {
    type: string;
    data: T;
}

let _dragItem: IDragItem<any> | null = null;

export function getDragItem<T>(): IDragItem<T> | null {
    return _dragItem;
}

export function setDragItem(type: string, data: any) {
    _dragItem = { type, data };
}

export function clearDragItem() {
    _dragItem = null;
}

export function DragItem(props: { type: string, data?: any; children: any; className?: string, style?: React.CSSProperties }) {
    const { type, data, children, className, style } = props;
    const ref = useRef<HTMLDivElement>(null);
    const onDragStart: DragEventHandler<HTMLDivElement> = (e) => {
        setDragItem(type, data);
        const div = (e.target as HTMLDivElement);
        div.classList.add("dragging");
        // let img = new Image();
        // img.src = 'example.gif';
        // ev.dataTransfer.setDragImage(img, 10, 10);
        // e.dataTransfer.dropEffect = "copy"; // copy | move | link
    };
    const onDragEnd: DragEventHandler<HTMLDivElement> = (e) => {
        clearDragItem();
        const div = (e.target as HTMLDivElement);
        div.classList.remove("dragging");
    };
    return <div ref={ref} className={className} style={style} draggable onDragStart={onDragStart} onDragEnd={onDragEnd}>{children}</div>;
}

// DropZone.tsx
export interface IOnDroppedEvent<T, U> {
    x: number;
    y: number;
    pageX: number;
    pageY: number;
    item: T,
    data: U,
    target: EventTarget;
}

export function DropZone<T, U>(props: { types: string[], children: any, onDropped: (e: IOnDroppedEvent<T, U>) => void, className?: string, style?: React.CSSProperties, item: T }) {
    const { types, onDropped, children, className, style, item } = props;
    const ref = useRef<HTMLDivElement>(null);
    const onDrop: DragEventHandler<HTMLDivElement> = (e) => {
        e.preventDefault();
        const dragItem = getDragItem<U>();
        if (!dragItem) return false;
        if (!types.includes(dragItem.type)) return false;
        e.stopPropagation();
        const div = ref.current!;
        const rect = div.getBoundingClientRect();
        const event: IOnDroppedEvent<T, U> = {
            x: e.pageX - rect.left,
            y: e.pageY - rect.top,
            pageX: e.pageX,
            pageY: e.pageY,
            item: item,
            data: dragItem.data,
            target: e.target
        };
        onDropped(event);
        div.classList.remove("drag-over");
    };
    const onDragOver: DragEventHandler<HTMLDivElement> = (e) => {
        const dragItem = getDragItem<U>();
        if (!dragItem) return false;
        if (!types.includes(dragItem.type)) return false;
        const div = ref.current!;
        div.classList.add("drag-over");
        e.preventDefault(); // allow drop
        e.stopPropagation();
        return false;
    };

    const onDragEnter: DragEventHandler<HTMLDivElement> = (e) => {
        const dragItem = getDragItem<U>();
        if (!dragItem) return false;
        if (!types.includes(dragItem.type)) return false;
        const div = ref.current!;
        div.classList.add("drag-over");
        e.preventDefault(); // allow drop
        e.stopPropagation();
        return false;
    };
    const onDragLeave: DragEventHandler<HTMLDivElement> = (e) => {
        const div = ref.current!;
        div.classList.remove("drag-over");
        return false;
    };
    return <div ref={ref} className={className} style={style}
        onDrop={onDrop}
        onDragOver={onDragOver}
        onDragEnter={onDragEnter}
        onDragLeave={onDragLeave}>{children}</div>;
}


56820cookie-checkReact: Drag Drop (Custom Implementation)