import { cn } from "@/utils/cn"; import React, { useRef, useState, useEffect, useCallback } from "react"; export function JsonErrorHighlighter({ value, error, onChange, onBlur, className, style, }: { value: string; error: { line?: number; column?: number; message: string } | null; onChange: (e: React.ChangeEvent) => void; onBlur?: () => void; className?: string; style?: React.CSSProperties; }) { const textareaRef = useRef(null); const preRef = useRef(null); const lineNumbersRef = useRef(null); const [scrollInfo, setScrollInfo] = useState({ firstVisible: 0, lastVisible: 20, scrollTop: 0, lineHeight: 24, clientHeight: 250, }); const lines = value.split("\n"); const errorLineIdx = (error?.line ?? 1) - 1; // Calculate visible lines on scroll or resize const recalcVisibleLines = useCallback(() => { const textarea = textareaRef.current; if (!textarea) return; const lineHeight = parseFloat(getComputedStyle(textarea).lineHeight) || 24; const scrollTop = textarea.scrollTop; const clientHeight = textarea.clientHeight; const firstVisible = Math.floor(scrollTop / lineHeight); const lastVisible = Math.min( lines.length - 1, Math.ceil((scrollTop + clientHeight) / lineHeight), ); setScrollInfo({ firstVisible, lastVisible, scrollTop, lineHeight, clientHeight, }); }, [lines.length]); useEffect(() => { recalcVisibleLines(); // Sync overlay height with textarea const handleResize = () => recalcVisibleLines(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, [value, recalcVisibleLines]); // Attach scroll handler const handleScroll = () => { recalcVisibleLines(); if (textareaRef.current && preRef.current) { preRef.current.scrollTop = textareaRef.current.scrollTop; preRef.current.scrollLeft = textareaRef.current.scrollLeft; } }; // Only render visible lines in
  const visibleLines = lines.slice(
    scrollInfo.firstVisible,
    scrollInfo.lastVisible + 1,
  );

  return (
    
{/* Highlight overlay */} {error?.line && ( )} {/* Line numbers overlay */}
{visibleLines.map((_, idx) => { const globalIdx = idx + scrollInfo.firstVisible; return (
{globalIdx + 1}
); })}
{/* Textarea */}