packages/react-moveable/src/ables/DragArea.tsx

import {
    createWarpMatrix,
} from "@scena/matrix";
import { ref } from "framework-utils";
import { getRect, calculateInversePosition, makeMatrixCSS, prefix } from "../utils";
import {
    Renderer, GroupableProps, DragAreaProps, MoveableManagerInterface, MoveableGroupInterface,
} from "../types";
import { AREA_PIECE, AVOID, AREA_PIECES } from "../classNames";
import { addClass, removeClass, requestAnimationFrame } from "@daybrush/utils";

function restoreStyle(moveable: MoveableManagerInterface) {
    const el = moveable.areaElement;

    if (!el) {
        return;
    }
    const { width, height } = moveable.state;

    removeClass(el, AVOID);

    el.style.cssText += `left: 0px; top: 0px; width: ${width}px; height: ${height}px`;
}

function renderPieces(React: Renderer): any {
    return (<div key="area_pieces" className={AREA_PIECES}>
        <div className={AREA_PIECE}></div>
        <div className={AREA_PIECE}></div>
        <div className={AREA_PIECE}></div>
        <div className={AREA_PIECE}></div>
    </div>);
}
export default {
    name: "dragArea",
    props: [
        "dragArea",
        "passDragArea",
    ] as const,
    events: [
        "click",
        "clickGroup",
    ] as const,
    render(moveable: MoveableManagerInterface<GroupableProps>, React: Renderer): any[] {
        const { target, dragArea, groupable, passDragArea } = moveable.props;
        const { width, height, renderPoses } = moveable.getState();

        const className = passDragArea ? prefix("area", "pass") : prefix("area");
        if (groupable) {
            return [
                <div key="area" ref={ref(moveable, "areaElement")} className={className}></div>,
                renderPieces(React),
            ];
        }
        if (!target || !dragArea) {
            return [];
        }
        const h = createWarpMatrix(
            [0, 0],
            [width, 0],
            [0, height],
            [width, height],
            renderPoses[0],
            renderPoses[1],
            renderPoses[2],
            renderPoses[3],
        );
        const transform = h.length ? makeMatrixCSS(h, true) : "none";

        return [
            <div key="area" ref={ref(moveable, "areaElement")} className={className} style={{
                top: "0px",
                left: "0px",
                width: `${width}px`,
                height: `${height}px`,
                transformOrigin: "0 0",
                transform,
            }}></div>,
            renderPieces(React),
        ];
    },
    dragStart(moveable: MoveableManagerInterface, { datas, clientX, clientY, inputEvent }: any) {
        if (!inputEvent) {
            return false;
        }
        datas.isDragArea = false;
        const areaElement = moveable.areaElement;
        const state = moveable.state;
        const {
            moveableClientRect,
            renderPoses,
            rootMatrix,
            is3d,
        } = state;
        const { left, top } = moveableClientRect;
        const {
            left: relativeLeft,
            top: relativeTop,
            width,
            height,
        } = getRect(renderPoses);
        const n = is3d ? 4 : 3;
        let [posX, posY] = calculateInversePosition(rootMatrix, [clientX - left, clientY - top], n);

        posX -= relativeLeft;
        posY -= relativeTop;
        const rects = [
            { left: relativeLeft, top: relativeTop, width, height: posY - 10 },
            { left: relativeLeft, top: relativeTop, width: posX - 10, height },
            { left: relativeLeft, top: relativeTop + posY + 10, width, height: height - posY - 10 },
            { left: relativeLeft + posX + 10, top: relativeTop, width: width - posX - 10, height },
        ];

        const children = [].slice.call(areaElement.nextElementSibling!.children) as HTMLElement[];
        rects.forEach((rect, i) => {
            children[i].style.cssText
                = `left: ${rect.left}px;top: ${rect.top}px; width: ${rect.width}px; height: ${rect.height}px;`;
        });
        addClass(areaElement, AVOID);
        state.disableNativeEvent = true;
        return;
    },
    drag(moveable: MoveableManagerInterface, { datas, inputEvent }: any) {
        this.enableNativeEvent(moveable);
        if (!inputEvent) {
            return false;
        }
        if (!datas.isDragArea) {
            datas.isDragArea = true;
            restoreStyle(moveable);
        }
    },
    dragEnd(moveable: MoveableManagerInterface<DragAreaProps>, e: any) {
        this.enableNativeEvent(moveable);
        const { inputEvent, datas } = e;
        if (!inputEvent) {
            return false;
        }
        if (!datas.isDragArea) {
            restoreStyle(moveable);
        }
    },
    dragGroupStart(moveable: MoveableGroupInterface, e: any) {
        return this.dragStart(moveable, e);
    },
    dragGroup(moveable: MoveableGroupInterface, e: any) {
        return this.drag(moveable, e);
    },
    dragGroupEnd(
        moveable: MoveableGroupInterface<DragAreaProps>,
        e: any,
    ) {
        return this.dragEnd(moveable, e);
    },
    unset(moveable: MoveableManagerInterface<DragAreaProps>) {
        restoreStyle(moveable);
        moveable.state.disableNativeEvent = false;
    },
    enableNativeEvent(moveable: MoveableManagerInterface<DragAreaProps>) {
        const state = moveable.state;
        if (state.disableNativeEvent) {
            requestAnimationFrame(() => {
                state.disableNativeEvent = false;
            });
        }
    },
};

/**
 * Add an event to the moveable area instead of the target for stopPropagation. (default: false, true in group)
 * @name Moveable#dragArea
 * @example
 * import Moveable from "moveable";
 *
 * const moveable = new Moveable(document.body, {
 *  dragArea: false,
 * });
 */
/**
 * Set `pointerEvents: none;` css to pass events in dragArea. (default: false)
 * @name Moveable#passDragArea
 * @example
 * import Moveable from "moveable";
 *
 * const moveable = new Moveable(document.body, {
 *  dragArea: false,
 * });
 */