const jsonToken = /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false)\b|\b(null)\b|([{}\[\],:])/g; import { useEffect, useMemo, useRef } from "react"; export function highlightJson(source: string) { const parts = []; let cursor = 0; for (const match of source.matchAll(jsonToken)) { const index = match.index; if (index > cursor) parts.push(source.slice(cursor, index)); const kind = match[1] ? "key" : match[2] ? "string" : match[3] ? "number" : match[4] ? "boolean" : match[5] ? "null" : "punctuation"; parts.push({match[0]}); cursor = index + match[0].length; } parts.push(source.slice(cursor)); return parts; } export function JsonViewer({ value, activeNode, activeQuestion, isOpen = true }: { value: unknown; activeNode?: string; activeQuestion?: string; isOpen?: boolean }) { const containerRef = useRef(null); const activeRef = useRef(null); const lines = useMemo(() => (JSON.stringify(value, null, 2) ?? "null").split("\n"), [value]); // IDs are globally unique for fields and unique within pages. Match the object containing its id. const rangeFor = (id: string | undefined, page = false): [number, number] => { if (!id) return [-1, -1]; const idLine = lines.findIndex(line => line.trim() === `"id": ${JSON.stringify(id)},` && (!page || line.startsWith(' "id"'))); if (idLine < 0) return [-1, -1]; const indent = lines[idLine].length - lines[idLine].trimStart().length - 2; const start = idLine - 1; const end = lines.findIndex((line, i) => i > idLine && (line === " ".repeat(indent) + "}" || line === " ".repeat(indent) + "},")); return [start, end]; }; const [start, end] = rangeFor(activeNode, true); const [questionStart, questionEnd] = rangeFor(activeQuestion); const scrollTarget = questionStart >= 0 ? questionStart : start; useEffect(() => { const container = containerRef.current; const active = activeRef.current; if (isOpen && container && active) { container.scrollTop += active.getBoundingClientRect().top - container.getBoundingClientRect().top - 16; } }, [activeNode, activeQuestion, isOpen, lines]); return (
      {lines.map((line, index) => (
        = 0 && index >= start && index <= end ? " json-active-node" : ""}${questionStart >= 0 && index >= questionStart && index <= questionEnd ? " json-active-question" : ""}`}
          aria-current={index === scrollTarget ? "location" : undefined}
        >{highlightJson(line)}{"\n"}
      ))}
    
); }