"use client" import { memo, useRef, type ReactNode } from "react" import { createPortal } from "react-dom" import { cn } from "@/lib/utils" export type DocsRulerDragTooltipState = { label: string x: number y: number } | null export const DocsRulerMarginDragTooltip = memo(function DocsRulerMarginDragTooltip({ tooltip, }: { tooltip: DocsRulerDragTooltipState }) { if (!tooltip || typeof document === "undefined") return null return createPortal(
{tooltip.label}
, document.body ) }) /** Blue downward triangle (margin / left indent). */ export const DocsRulerTriangleMarker = memo(function DocsRulerTriangleMarker({ left, className, }: { left: number className?: string }) { return (
) }) /** Blue rectangle on horizontal ruler (first-line indent). */ export const DocsRulerFirstLineMarker = memo(function DocsRulerFirstLineMarker({ left, }: { left: number }) { return (
) }) /** Blue upward triangle on vertical ruler (top margin). */ export const DocsRulerUpTriangleMarker = memo(function DocsRulerUpTriangleMarker({ top, }: { top: number }) { return (
) }) /** Blue downward triangle on vertical ruler (bottom margin). */ export const DocsRulerDownTriangleMarker = memo(function DocsRulerDownTriangleMarker({ top, }: { top: number }) { return (
) }) export const DocsRulerDraggableHandle = memo(function DocsRulerDraggableHandle({ style, className, disabled, axis, ariaLabel, onPointerDown, children, }: { style: React.CSSProperties className?: string disabled?: boolean axis: "horizontal" | "vertical" ariaLabel: string onPointerDown: (event: React.PointerEvent) => void children: ReactNode }) { return (
{children}
) }) export function useRulerPointerDrag({ rulerRef, axis, disabled, onDrag, onDragEnd, }: { rulerRef: React.RefObject axis: "horizontal" | "vertical" disabled?: boolean onDrag: (pagePx: number, clientX: number, clientY: number) => void onDragEnd: () => void }) { const draggingRef = useRef(false) const onPointerDown = (event: React.PointerEvent) => { if (disabled) return event.preventDefault() event.stopPropagation() const handle = event.currentTarget handle.setPointerCapture(event.pointerId) draggingRef.current = true document.body.style.userSelect = "none" document.body.style.cursor = axis === "horizontal" ? "ew-resize" : "ns-resize" const readPagePx = (clientX: number, clientY: number) => { const ruler = rulerRef.current if (!ruler) return null const rect = ruler.getBoundingClientRect() const scaleAttr = ruler.dataset.docsRulerScale const scale = scaleAttr ? Number.parseFloat(scaleAttr) : 1 if (!Number.isFinite(scale) || scale <= 0) return null return axis === "horizontal" ? (clientX - rect.left) / scale : (clientY - rect.top) / scale } const move = (clientX: number, clientY: number) => { const pagePx = readPagePx(clientX, clientY) if (pagePx != null) onDrag(pagePx, clientX, clientY) } move(event.clientX, event.clientY) const onMove = (ev: PointerEvent) => move(ev.clientX, ev.clientY) const onUp = (ev: PointerEvent) => { draggingRef.current = false document.body.style.userSelect = "" document.body.style.cursor = "" if (handle.hasPointerCapture(ev.pointerId)) { handle.releasePointerCapture(ev.pointerId) } window.removeEventListener("pointermove", onMove) window.removeEventListener("pointerup", onUp) window.removeEventListener("pointercancel", onUp) onDragEnd() } window.addEventListener("pointermove", onMove) window.addEventListener("pointerup", onUp) window.addEventListener("pointercancel", onUp) } return { onPointerDown, draggingRef } }