"use client"; import * as React from "react"; import type { Editor } from "@tiptap/react"; import { cn } from "@/lib/utils"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; type BlockDragHandleProps = { editor: Editor | null; containerRef: React.RefObject; }; /** * Visual drag handle on the left of the hovered top-level block. * Drag-to-reorder is not wired (would require custom NodeViews). */ export function BlockDragHandle({ editor, containerRef }: BlockDragHandleProps) { const [pos, setPos] = React.useState<{ top: number; left: number; } | null>(null); React.useEffect(() => { if (!editor) return; const root = editor.view.dom; const onMove = (e: MouseEvent) => { const coords = editor.view.posAtCoords({ left: e.clientX, top: e.clientY, }); if (!coords) { setPos(null); return; } const $pos = editor.state.doc.resolve(coords.pos); for (let d = $pos.depth; d > 0; d--) { const node = $pos.node(d); const parent = $pos.node(d - 1); if (parent.type.name === "doc" && node.isBlock) { const start = $pos.before(d); const el = editor.view.nodeDOM(start); if (el instanceof HTMLElement) { const cr = el.getBoundingClientRect(); const wrap = containerRef.current?.getBoundingClientRect(); if (wrap) { setPos({ top: cr.top - wrap.top + cr.height / 2 - 14, left: -2, }); } else { setPos({ top: cr.top + cr.height / 2 - 14, left: cr.left - 28, }); } } return; } } setPos(null); }; const onLeave = () => setPos(null); root.addEventListener("mousemove", onMove); root.addEventListener("mouseleave", onLeave); return () => { root.removeEventListener("mousemove", onMove); root.removeEventListener("mouseleave", onLeave); }; }, [editor, containerRef]); if (!editor || !pos) return null; return ( Drag to reorder (coming soon) ); }