diff --git a/src/tiny-dnd/dragable.tsx b/src/tiny-dnd/dragable.tsx index bd06e00..9010a6e 100644 --- a/src/tiny-dnd/dragable.tsx +++ b/src/tiny-dnd/dragable.tsx @@ -3,6 +3,7 @@ import { Slot } from "@radix-ui/react-slot"; import { ReactNode, useId, useRef } from "react"; import { createRoot } from "react-dom/client"; import { twMerge } from "tailwind-merge"; +import { useGhostElement } from "./use-ghost-element"; import { DRAGABLE_DRAGGING, DROPABLE_RECEIVING, setDataset } from "./utils"; export type DragData = { @@ -38,6 +39,7 @@ export function Dragable({ const id = useId(); const Comp = asChild ? Slot : "div"; const ref = useRef(null); // Used by the slot + useGhostElement(ref, renderDragLayer); const canHover = useCanHover(); function isSelf(e: React.DragEvent) { @@ -94,17 +96,32 @@ export function Dragable({ e.dataTransfer.effectAllowed = "move"; - if (renderDragLayer && canHover) { - /** This is the craziest hack I've ever made! */ - const el = document.createElement("div"); - const root = createRoot(el!); - document.body.appendChild(el); - root.render(<>{renderDragLayer(null)}); - console.log("elly!", el); - e.dataTransfer.setDragImage(el, 0, 0); - setTimeout(() => { - el.remove(); - }, 0); + if (false && renderDragLayer && canHover) { + // Plan: + // 1. Hide this element + // 2. Spawn a renderDragLayer element somewhere + if (ref.current) { + console.log("hacking stuff"); + ref.current.style.visibility = "hidden"; + const el = document.createElement("div"); + const root = createRoot(el!); + document.body.append(el); + el.style.position = "fixed"; + el.style.left = "0"; + el.style.top = "0"; + el.style.zIndex = "20"; + root.render(<>{renderDragLayer(null)}); + } + // /** This is the craziest hack I've ever made! */ + // const el = document.createElement("div"); + // const root = createRoot(el!); + // document.body.appendChild(el); + // root.render(<>{renderDragLayer(null)}); + // console.log("elly!", el); + // e.dataTransfer.setDragImage(el, 0, 0); + // setTimeout(() => { + // el.remove(); + // }, 0); } } diff --git a/src/tiny-dnd/use-ghost-element.tsx b/src/tiny-dnd/use-ghost-element.tsx new file mode 100644 index 0000000..218597b --- /dev/null +++ b/src/tiny-dnd/use-ghost-element.tsx @@ -0,0 +1,71 @@ +import { MutableRefObject, ReactNode, useCallback, useLayoutEffect, useMemo, useRef } from "react"; +import { createRoot } from "react-dom/client"; + +// TODO: Debounce! +export function useGhostElement(drag: MutableRefObject, render: ((parentEl: HTMLDivElement | null) => ReactNode) | undefined) { + const ghostRef = useRef(null); + useLayoutEffect(() => { + if (!render) return; + if (!drag || !drag.current) return; + const dc = drag.current; + + dc.addEventListener("dragstart", handleDragStart); + dc.addEventListener("dragend", handleDragEnd); + document.addEventListener("mousemove", debouncedHandleMouseMove); + return () => { + document.removeEventListener("mousemove", debouncedHandleMouseMove); + dc.removeEventListener("dragstart", handleDragStart); + dc.removeEventListener("dragend", handleDragEnd); + if (ghostRef.current) { + ghostRef.current.remove(); + } + }; + }, [drag, render]); + + const handleDragStart = useCallback( + (e: DragEvent) => { + if (!render) return; // No point if there's nothing to render + console.log("[ghost] dragstart", e.currentTarget); + if (!drag.current) return; + drag.current.style.visibility = "hidden"; + const el = document.createElement("div"); + const root = createRoot(el!); + document.body.append(el); + el.style.position = "fixed"; + el.style.left = "0"; + el.style.top = "0"; + el.style.zIndex = "20"; + el.style.userSelect = "none"; + el.style.transform = "translate(calc(1px * var(--x)), calc(1px * var(--y))"; + ghostRef.current = el; + // WHY IS GETTING MOUSE COORDINATES SO HARD?! + root.render(<>{render(null)}); + }, + [drag, render, ghostRef], + ); + + function handleDragEnd(e: DragEvent) { + if (ghostRef.current) { + ghostRef.current.remove(); + } + console.log("[ghost] drag-end"); + } + + const debouncedHandleMouseMove = useMemo(() => { + const handleMouseMove = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + window.requestAnimationFrame(() => { + if (!ghostRef.current) return; + document.documentElement.style.setProperty("--x", `${e.clientX}`); + document.documentElement.style.setProperty("--y", `${e.clientY}`); + //ghostRef.current.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`; + //ghostRef.current.style.left = `${e.clientX}`; + //ghostRef.current.style.top = `${e.clientY}`; + }); + }; + + //return debounce(handleMouseMove, 5); + return handleMouseMove; + }, []); +} diff --git a/src/tiny-dnd/utils.ts b/src/tiny-dnd/utils.ts index ba48f46..b347b67 100644 --- a/src/tiny-dnd/utils.ts +++ b/src/tiny-dnd/utils.ts @@ -42,3 +42,11 @@ export function setDataset({ ); } } + +export function debounce void>(func: T, wait: number): (...args: Parameters) => void { + let timeout: ReturnType | null = null; + return function (...args: Parameters) { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +}