{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"code-view","type":"registry:ui","title":"CodeView","description":"Lightweight syntax-highlighted source view (HTML highlighted; fills height). Powers the Editor Source toggle.","package":"react-fancy","dependencies":["@particle-academy\/fancy-file-commons"],"registryDependencies":[],"files":[{"path":"components\/fancy\/code-view\/CodeView.tsx","content":"import { useEffect, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport {\n  highlightCode,\n  tokenizeHtml,\n  LIGHT_COLORS,\n  DARK_COLORS,\n  type ThemeColors,\n} from \"@particle-academy\/fancy-file-commons\";\nimport { cn } from \"..\/..\/utils\/cn\";\nimport type { CodeViewProps } from \".\/CodeView.types\";\n\n\/\/ \u2500\u2500 Dark-mode detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ The highlight palette is baked in as inline colors, so we resolve light\/dark\n\/\/ ourselves. Follow BOTH signals so we match whatever drives the surrounding\n\/\/ `dark:` Tailwind variants: the `.dark` class (class strategy) OR the\n\/\/ prefers-color-scheme media query (media strategy).\n\nfunction subscribeDark(callback: () => void): () => void {\n  const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n  mq.addEventListener(\"change\", callback);\n  const observer = new MutationObserver(callback);\n  observer.observe(document.documentElement, {\n    attributes: true,\n    attributeFilter: [\"class\"],\n  });\n  return () => {\n    mq.removeEventListener(\"change\", callback);\n    observer.disconnect();\n  };\n}\n\nfunction darkSnapshot(): boolean {\n  return (\n    document.documentElement.classList.contains(\"dark\") ||\n    window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n  );\n}\n\nfunction useIsDark(): boolean {\n  return useSyncExternalStore(subscribeDark, darkSnapshot, () => false);\n}\n\n\/\/ Shared text metrics \u2014 the overlay and the textarea MUST match exactly (font,\n\/\/ size, line-height, padding, wrapping) so the highlighted glyphs sit under the\n\/\/ caret. Keep this string identical on both layers.\nconst TEXT_METRICS = \"m-0 p-3 font-mono text-xs leading-relaxed\";\n\n\/**\n * A lightweight, syntax-highlighted source view built on the pure highlight\n * primitives in `@particle-academy\/fancy-file-commons` \u2014 a highlight overlay\n * with a transparent, auto-growing textarea on top; the container scrolls both\n * together. Fills its parent's height (`h-full`) and grows with content down to\n * `minHeight`. Only HTML is highlighted; other languages render as plain text.\n *\/\nexport function CodeView({\n  value,\n  onChange,\n  language = \"plaintext\",\n  readOnly = false,\n  placeholder,\n  minHeight = 120,\n  maxHeight,\n  className,\n}: CodeViewProps) {\n  const textareaRef = useRef<HTMLTextAreaElement | null>(null);\n  const isDark = useIsDark();\n  const colors: ThemeColors = isDark ? DARK_COLORS : LIGHT_COLORS;\n\n  const highlighted = useMemo(() => {\n    const tokens = language === \"html\" ? tokenizeHtml(value) : [];\n    return highlightCode(value, tokens, colors);\n  }, [value, language, colors]);\n\n  \/\/ Grow the textarea to its content height so it never scrolls internally \u2014\n  \/\/ the outer container owns scrolling and moves overlay + textarea in lockstep.\n  useEffect(() => {\n    const el = textareaRef.current;\n    if (!el) return;\n    el.style.height = \"auto\";\n    el.style.height = `${el.scrollHeight}px`;\n  }, [value]);\n\n  const editable = !readOnly && !!onChange;\n\n  return (\n    <div\n      data-react-fancy-code-view=\"\"\n      \/\/ No baked-in height: the consumer picks the fill mechanism \u2014 `flex-1`\n      \/\/ inside a flex column (the Editor source body), or `h-full` when the\n      \/\/ parent has a definite height. A hard-coded `h-full` here collapses to\n      \/\/ content height under a flex-computed (indefinite) parent.\n      className={cn(\"relative overflow-auto\", className)}\n      style={{ backgroundColor: colors.background, color: colors.foreground, minHeight, maxHeight }}\n    >\n      <div className=\"relative min-h-full\">\n        {\/* Highlight overlay *\/}\n        <pre\n          aria-hidden=\"true\"\n          className={cn(\"pointer-events-none absolute inset-0 overflow-hidden border-none\", TEXT_METRICS)}\n          style={{ whiteSpace: \"pre-wrap\", overflowWrap: \"break-word\" }}\n        >\n          <code dangerouslySetInnerHTML={{ __html: highlighted + \"\\n\" }} \/>\n        <\/pre>\n\n        {\/* Placeholder *\/}\n        {placeholder && value.length === 0 && (\n          <div className={cn(\"pointer-events-none absolute left-0 top-0 opacity-40\", TEXT_METRICS)}>\n            {placeholder}\n          <\/div>\n        )}\n\n        {\/* Transparent input layer (caret + selection visible, glyphs from overlay) *\/}\n        <textarea\n          ref={textareaRef}\n          data-react-fancy-code-view-input=\"\"\n          value={value}\n          onChange={editable ? (e) => onChange!(e.target.value) : undefined}\n          readOnly={!editable}\n          spellCheck={false}\n          autoComplete=\"off\"\n          autoCorrect=\"off\"\n          autoCapitalize=\"off\"\n          aria-label={placeholder}\n          className={cn(\n            \"relative block w-full resize-none border-none bg-transparent text-transparent outline-none\",\n            TEXT_METRICS,\n          )}\n          style={{\n            caretColor: colors.cursorColor,\n            overflow: \"hidden\",\n            whiteSpace: \"pre-wrap\",\n            overflowWrap: \"break-word\",\n            minHeight,\n          }}\n        \/>\n      <\/div>\n    <\/div>\n  );\n}\n\nCodeView.displayName = \"CodeView\";\n","type":"registry:ui","target":"components\/fancy\/code-view\/CodeView.tsx"},{"path":"components\/fancy\/code-view\/CodeView.types.ts","content":"export interface CodeViewProps {\n  \/** Source text to display \/ edit. Controlled. *\/\n  value: string;\n  \/**\n   * Called on edit. Omit (or set `readOnly`) for a read-only view. When\n   * provided, the view is an editable source surface (transparent textarea over\n   * the highlight overlay).\n   *\/\n  onChange?: (value: string) => void;\n  \/**\n   * Language id for syntax highlighting. Only `\"html\"` is highlighted (the one\n   * grammar shipped by `fancy-file-commons`); everything else \u2014 including\n   * `\"markdown\"` and `\"plaintext\"` \u2014 renders un-highlighted. For richer language\n   * highlighting use `@particle-academy\/fancy-code`'s `CodeEditor`.\n   * @default \"plaintext\"\n   *\/\n  language?: \"html\" | \"markdown\" | \"plaintext\" | (string & {});\n  \/** Read-only mode (no editing even if `onChange` is passed). @default false *\/\n  readOnly?: boolean;\n  \/** Placeholder shown when empty. *\/\n  placeholder?: string;\n  \/** Min height in px before the view grows with content. @default 120 *\/\n  minHeight?: number;\n  \/** Max height in px before the view scrolls internally. *\/\n  maxHeight?: number;\n  \/** Additional classes on the scroll container (e.g. `h-full`, `flex-auto`). *\/\n  className?: string;\n}\n","type":"registry:ui","target":"components\/fancy\/code-view\/CodeView.types.ts"},{"path":"components\/fancy\/code-view\/index.ts","content":"export { CodeView } from \".\/CodeView\";\nexport type { CodeViewProps } from \".\/CodeView.types\";\n","type":"registry:ui","target":"components\/fancy\/code-view\/index.ts"}]}