{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-jsx-preview",
  "title": "AI: Jsx Preview",
  "description": "A streamed JSX preview with incomplete-tag recovery, a last valid render and an error state.",
  "dependencies": [
    "@untitledui/icons@^0.0.23",
    "react-jsx-parser@^2.4.1"
  ],
  "registryDependencies": [
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/ai/jsx-preview.tsx",
      "content": "// Ported from vercel/ai-elements@6a9d5b1822ffb10bba4bd97175f01edd7d8651cd.\n\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AlertCircle as AlertCircle } from \"@untitledui/icons\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport {\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport type { TProps as JsxParserProps } from \"react-jsx-parser\";\nimport JsxParser from \"react-jsx-parser\";\n\ninterface JSXPreviewContextValue {\n  jsx: string;\n  processedJsx: string;\n  isStreaming: boolean;\n  error: Error | null;\n  setError: (error: Error | null) => void;\n  setLastGoodJsx: (jsx: string) => void;\n  components: JsxParserProps[\"components\"];\n  bindings: JsxParserProps[\"bindings\"];\n  onErrorProp?: (error: Error) => void;\n}\n\nconst JSXPreviewContext = createContext<JSXPreviewContextValue | null>(null);\n\nconst TAG_REGEX = /<\\/?([a-zA-Z][a-zA-Z0-9]*)\\s*([^>]*?)(\\/)?>/;\n\nexport const useJSXPreview = () => {\n  const context = useContext(JSXPreviewContext);\n  if (!context) {\n    throw new Error(\"JSXPreview components must be used within JSXPreview\");\n  }\n  return context;\n};\n\nconst matchJsxTag = (code: string) => {\n  if (code.trim() === \"\") {\n    return null;\n  }\n\n  const match = code.match(TAG_REGEX);\n\n  if (!match || match.index === undefined) {\n    return null;\n  }\n\n  const [fullMatch, tagName, attributes, selfClosing] = match;\n  if (!tagName) return null;\n\n  let type: \"self-closing\" | \"closing\" | \"opening\";\n  if (selfClosing) {\n    type = \"self-closing\";\n  } else if (fullMatch.startsWith(\"</\")) {\n    type = \"closing\";\n  } else {\n    type = \"opening\";\n  }\n\n  return {\n    attributes: (attributes ?? \"\").trim(),\n    endIndex: match.index + fullMatch.length,\n    startIndex: match.index,\n    tag: fullMatch,\n    tagName,\n    type,\n  };\n};\n\nconst stripIncompleteTag = (text: string) => {\n  // Find the last '<' that isn't part of a complete tag\n  const lastOpen = text.lastIndexOf(\"<\");\n  if (lastOpen === -1) {\n    return text;\n  }\n\n  const afterOpen = text.slice(lastOpen);\n  // If there's no closing '>' after the last '<', it's an incomplete tag\n  if (!afterOpen.includes(\">\")) {\n    return text.slice(0, lastOpen);\n  }\n\n  return text;\n};\n\nconst completeJsxTag = (code: string) => {\n  const stack: string[] = [];\n  let result = \"\";\n  let currentPosition = 0;\n\n  while (currentPosition < code.length) {\n    const match = matchJsxTag(code.slice(currentPosition));\n    if (!match) {\n      // No more tags found, strip any trailing incomplete tag\n      result += stripIncompleteTag(code.slice(currentPosition));\n      break;\n    }\n    const { tagName, type, endIndex } = match;\n\n    // Include any text content before this tag\n    result += code.slice(currentPosition, currentPosition + endIndex);\n\n    if (type === \"opening\") {\n      stack.push(tagName);\n    } else if (type === \"closing\") {\n      stack.pop();\n    }\n\n    currentPosition += endIndex;\n  }\n\n  return (\n    result +\n    [...stack]\n      .reverse()\n      .map((tag) => `</${tag}>`)\n      .join(\"\")\n  );\n};\n\nexport type JSXPreviewProps = ComponentProps<\"div\"> & {\n  jsx: string;\n  isStreaming?: boolean;\n  components?: JsxParserProps[\"components\"];\n  bindings?: JsxParserProps[\"bindings\"];\n  onError?: (error: Error) => void;\n};\n\nexport const JSXPreview = memo(\n  ({\n    jsx,\n    isStreaming = false,\n    components,\n    bindings,\n    onError,\n    className,\n    children,\n    ...props\n  }: JSXPreviewProps) => {\n    const [prevJsx, setPrevJsx] = useState(jsx);\n    const [error, setError] = useState<Error | null>(null);\n    const [_lastGoodJsx, setLastGoodJsx] = useState(\"\");\n\n    // Clear error when jsx changes (derived state pattern)\n    if (jsx !== prevJsx) {\n      setPrevJsx(jsx);\n      setError(null);\n    }\n\n    const processedJsx = useMemo(\n      () => (isStreaming ? completeJsxTag(jsx) : jsx),\n      [jsx, isStreaming]\n    );\n\n    const contextValue = useMemo(\n      () => ({\n        bindings,\n        components,\n        error,\n        isStreaming,\n        jsx,\n        onErrorProp: onError,\n        processedJsx,\n        setError,\n        setLastGoodJsx,\n      }),\n      [\n        bindings,\n        components,\n        error,\n        isStreaming,\n        jsx,\n        onError,\n        processedJsx,\n        setError,\n      ]\n    );\n\n    return (\n      <JSXPreviewContext.Provider value={contextValue}>\n        <div className={cn(\"relative\", className)} {...props}>\n          {children}\n        </div>\n      </JSXPreviewContext.Provider>\n    );\n  }\n);\n\nJSXPreview.displayName = \"JSXPreview\";\n\nexport type JSXPreviewContentProps = Omit<ComponentProps<\"div\">, \"children\">;\n\nexport const JSXPreviewContent = memo(\n  ({ className, ...props }: JSXPreviewContentProps) => {\n    const {\n      processedJsx,\n      isStreaming,\n      components,\n      bindings,\n      setError,\n      setLastGoodJsx,\n      onErrorProp,\n    } = useJSXPreview();\n    const errorReportedRef = useRef<string | null>(null);\n    const lastGoodJsxRef = useRef(\"\");\n    const [hadError, setHadError] = useState(false);\n\n    // Reset error tracking when jsx changes\n    useEffect(() => {\n      errorReportedRef.current = null;\n      setHadError(false);\n    }, [processedJsx]);\n\n    const handleError = useCallback(\n      (err: Error) => {\n        // Prevent duplicate error reports for the same jsx\n        if (errorReportedRef.current === processedJsx) {\n          return;\n        }\n        errorReportedRef.current = processedJsx;\n\n        // During streaming, suppress errors and fall back to last good JSX\n        if (isStreaming) {\n          setHadError(true);\n          return;\n        }\n\n        setError(err);\n        onErrorProp?.(err);\n      },\n      [processedJsx, isStreaming, onErrorProp, setError]\n    );\n\n    // Track the last JSX that rendered without error\n    useEffect(() => {\n      if (!errorReportedRef.current) {\n        lastGoodJsxRef.current = processedJsx;\n        setLastGoodJsx(processedJsx);\n      }\n    }, [processedJsx, setLastGoodJsx]);\n\n    // During streaming, if the current JSX errored, re-render with last good version\n    const displayJsx =\n      isStreaming && hadError ? lastGoodJsxRef.current : processedJsx;\n\n    return (\n      <div className={cn(\"jsx-preview-content\", className)} {...props}>\n        <JsxParser\n          bindings={bindings}\n          components={components}\n          jsx={displayJsx}\n          onError={handleError}\n          renderInWrapper={false}\n        />\n      </div>\n    );\n  }\n);\n\nJSXPreviewContent.displayName = \"JSXPreviewContent\";\n\nexport type JSXPreviewErrorProps = ComponentProps<\"div\"> & {\n  children?: ReactNode | ((error: Error) => ReactNode);\n};\n\nconst renderChildren = (\n  children: ReactNode | ((error: Error) => ReactNode),\n  error: Error\n): ReactNode => {\n  if (typeof children === \"function\") {\n    return children(error);\n  }\n  return children;\n};\n\nexport const JSXPreviewError = memo(\n  ({ className, children, ...props }: JSXPreviewErrorProps) => {\n    const { error } = useJSXPreview();\n\n    if (!error) {\n      return null;\n    }\n\n    return (\n      <div\n        className={cn(\n          \"flex items-center gap-2 rounded-md border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm\",\n          className\n        )}\n        {...props}\n      >\n        {children ? (\n          renderChildren(children, error)\n        ) : (\n          <>\n            <AlertCircle className=\"size-4 shrink-0\" />\n            <span>{error.message}</span>\n          </>\n        )}\n      </div>\n    );\n  }\n);\n\nJSXPreviewError.displayName = \"JSXPreviewError\";\n",
      "type": "registry:ui",
      "target": "components/ai/jsx-preview.tsx"
    }
  ],
  "type": "registry:ui"
}