{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code-block",
  "description": "A code-block component.",
  "dependencies": [
    "@icons-pack/react-simple-icons",
    "shiki",
    "@hugeicons/react",
    "@hugeicons/core-free-icons",
    "hast-util-to-jsx-runtime"
  ],
  "registryDependencies": [
    "@cubby-ui/copy-button",
    "@cubby-ui/tabs",
    "@cubby-ui/scroll-area",
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/code-block/code-block.tsx",
      "content": "\"use client\";\n\nimport {\n  useLayoutEffect,\n  useState,\n  useMemo,\n  createContext,\n  useContext,\n} from \"react\";\nimport * as React from \"react\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { highlight } from \"@/registry/default/code-block/lib/shiki-shared\";\nimport { stripDiffMarker } from \"@/registry/default/code-block/lib/transformers/utils\";\nimport { cn } from \"@/lib/utils\";\nimport { solidSurface } from \"@/registry/default/lib/elevated\";\nimport { CopyButton } from \"@/registry/default/copy-button/copy-button\";\nimport {\n  SiTypescript,\n  SiJavascript,\n  SiPython,\n} from \"@icons-pack/react-simple-icons\";\nimport type { BundledLanguage } from \"shiki/langs\";\nimport { Tabs, TabsList, TabsTrigger } from \"@/registry/default/tabs/tabs\";\nimport {\n  ScrollArea,\n  type FadeEdges,\n} from \"@/registry/default/scroll-area/scroll-area\";\n\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport ComputerTerminal01Icon from \"@hugeicons/core-free-icons/ComputerTerminal01Icon\";\n\ninterface CodeBlockContextValue {\n  code: string;\n  language: string;\n  nodes: React.ReactElement | undefined;\n  lines: string[];\n  hasFocus: boolean;\n  showDiff: boolean;\n  floatingCopy: boolean;\n}\n\nconst CodeBlockContext = createContext<CodeBlockContextValue | null>(null);\n\nfunction useCodeBlock() {\n  const context = useContext(CodeBlockContext);\n  if (!context) {\n    throw new Error(\n      \"CodeBlock subcomponents must be used within a CodeBlock component\",\n    );\n  }\n  return context;\n}\n\ntype LanguageIconRenderer = React.ComponentType<{\n  size: number;\n  className: string;\n}>;\n\nfunction renderHugeicon(\n  icon: typeof ComputerTerminal01Icon,\n): LanguageIconRenderer {\n  function HugeLanguageIcon({\n    size,\n    className,\n  }: {\n    size: number;\n    className: string;\n  }) {\n    return (\n      <HugeiconsIcon\n        icon={icon}\n        size={size}\n        className={className}\n        strokeWidth={2}\n      />\n    );\n  }\n  return HugeLanguageIcon;\n}\n\nconst LANGUAGE_ICONS: Record<string, LanguageIconRenderer> = {\n  typescript: SiTypescript,\n  ts: SiTypescript,\n  tsx: SiTypescript,\n  javascript: SiJavascript,\n  js: SiJavascript,\n  jsx: SiJavascript,\n  bash: renderHugeicon(ComputerTerminal01Icon),\n  sh: renderHugeicon(ComputerTerminal01Icon),\n  shell: renderHugeicon(ComputerTerminal01Icon),\n  python: SiPython,\n  py: SiPython,\n};\n\nfunction getLanguageIcon(language: string) {\n  const normalized = language.toLowerCase();\n  const Icon = LANGUAGE_ICONS[normalized];\n\n  if (Icon) {\n    return <Icon size={16} className=\"text-muted-foreground\" />;\n  }\n\n  return <span className=\"text-muted-foreground text-sm\">{language}</span>;\n}\n\ninterface CodeBlockProps extends Omit<\n  useRender.ComponentProps<\"div\">,\n  \"children\"\n> {\n  code: string;\n  language?: string;\n  initial?: React.ReactElement;\n  floatingCopy?: boolean;\n  highlightLines?: number[] | string;\n  showDiff?: boolean;\n  focusLines?: number[] | string;\n  children: React.ReactNode;\n}\n\nfunction CodeBlock({\n  code,\n  language = \"javascript\",\n  initial,\n  floatingCopy = false,\n  highlightLines,\n  showDiff,\n  focusLines,\n  className,\n  render,\n  children,\n  ...props\n}: CodeBlockProps) {\n  const [nodes, setNodes] = useState(initial);\n\n  // Update during render (React 18+ state-from-props pattern) to avoid a\n  // cascading effect render when the `initial` prop changes.\n  const [prevInitial, setPrevInitial] = useState(initial);\n  if (initial !== prevInitial) {\n    setPrevInitial(initial);\n    if (initial) {\n      setNodes(initial);\n    }\n  }\n\n  const lines = useMemo(() => code.split(\"\\n\"), [code]);\n\n  useLayoutEffect(() => {\n    if (!initial) {\n      const normalizedLanguage = (language as BundledLanguage) || \"javascript\";\n      void highlight(code, normalizedLanguage, {\n        highlightLines,\n        showDiff,\n        focusLines,\n      }).then(setNodes);\n    }\n  }, [code, language, initial, highlightLines, showDiff, focusLines]);\n\n  const contextValue = useMemo(\n    () => ({\n      language,\n      code,\n      nodes,\n      lines,\n      hasFocus: !!focusLines,\n      showDiff: !!showDiff,\n      floatingCopy,\n    }),\n    [language, code, nodes, lines, focusLines, showDiff, floatingCopy],\n  );\n\n  const content = (\n    <CodeBlockContext.Provider value={contextValue}>\n      {children}\n    </CodeBlockContext.Provider>\n  );\n\n  const defaultProps = {\n    \"data-slot\": \"code-block\",\n    className: cn(\n      \"group max-w-full w-full rounded-2xl p-1 pt-0 relative\",\n      // bg-muted follows solidSurface to override its bg-surface-3.\n      solidSurface(3, 1),\n      \"bg-muted\",\n      // Restore top padding when there's no header.\n      \"has-[[data-slot='code-block-pre']:first-child]:pt-1\",\n      className,\n    ),\n    children: content,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ninterface CodeBlockHeaderProps\n  extends useRender.ComponentProps<\"div\">, Partial<BaseTabsProps> {\n  filename?: string;\n  tabVariant?: React.ComponentProps<typeof TabsList>[\"variant\"];\n  customIcon?: React.ReactNode;\n  showCopy?: boolean;\n}\n\nfunction CodeBlockHeader({\n  className,\n  render,\n  children,\n  filename,\n  tabs,\n  activeTab,\n  onTabChange,\n  tabVariant,\n  customIcon,\n  showCopy = true,\n  ...props\n}: CodeBlockHeaderProps) {\n  const context = useCodeBlock();\n  const language = context.language;\n  const code = context.code;\n\n  const startContent = (\n    <div className=\"flex min-w-0 items-center gap-2\">\n      {language && (\n        <CodeBlockLanguage language={language} customIcon={customIcon} />\n      )}\n      {filename && <CodeBlockFilename>{filename}</CodeBlockFilename>}\n      {tabs && activeTab && onTabChange && (\n        <CodeBlockTabs\n          tabs={tabs}\n          activeTab={activeTab}\n          onTabChange={onTabChange}\n          variant={tabVariant}\n        />\n      )}\n    </div>\n  );\n\n  const endContent = showCopy && code && (\n    <div className=\"flex items-center gap-2\">\n      <CopyButton data-slot=\"code-block-copy-button\" content={code} />\n    </div>\n  );\n\n  const content = children ?? (\n    <>\n      {startContent}\n      {endContent}\n    </>\n  );\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-header\",\n    className: cn(\n      \"flex items-center justify-between bg-transparent px-3 py-1\",\n      className,\n    ),\n    children: content,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ninterface CodeBlockLanguageProps extends useRender.ComponentProps<\"div\"> {\n  language: string;\n  customIcon?: React.ReactNode;\n}\n\nfunction CodeBlockLanguage({\n  language,\n  customIcon,\n  className,\n  render,\n  ...props\n}: CodeBlockLanguageProps) {\n  const icon = customIcon ?? getLanguageIcon(language);\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-language\",\n    className: cn(\"flex items-center gap-1.5\", className),\n    children: icon,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ntype CodeBlockFilenameProps = useRender.ComponentProps<\"span\">;\n\nfunction CodeBlockFilename({\n  className,\n  render,\n  children,\n  ...props\n}: CodeBlockFilenameProps) {\n  const defaultProps = {\n    \"data-slot\": \"code-block-filename\",\n    className: cn(\"text-sm font-medium\", className),\n    children,\n  };\n\n  const element = useRender({\n    defaultTagName: \"span\",\n    render,\n    props: mergeProps<\"span\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ninterface HeaderTab {\n  value: string;\n  label: string;\n}\n\ninterface BaseTabsProps {\n  tabs: HeaderTab[];\n  activeTab: React.ComponentProps<typeof Tabs>[\"value\"];\n  onTabChange: React.ComponentProps<typeof Tabs>[\"onValueChange\"];\n}\n\ninterface CodeBlockTabsProps\n  extends useRender.ComponentProps<\"div\">, BaseTabsProps {\n  variant?: React.ComponentProps<typeof TabsList>[\"variant\"];\n}\n\nfunction CodeBlockTabs({\n  tabs,\n  activeTab,\n  onTabChange,\n  variant = \"capsule\",\n  className,\n  render,\n  ...props\n}: CodeBlockTabsProps) {\n  const tabsElement = (\n    <Tabs value={activeTab} onValueChange={onTabChange} className=\"gap-1\">\n      <div className=\"scrollbar-hide flex max-w-full items-center overflow-x-auto\">\n        <TabsList\n          variant={variant}\n          size=\"small\"\n          className=\"w-max bg-transparent p-0 shadow-none ring-0\"\n        >\n          {tabs.map((tab) => (\n            <TabsTrigger key={tab.value} value={tab.value}>\n              {tab.label}\n            </TabsTrigger>\n          ))}\n        </TabsList>\n      </div>\n    </Tabs>\n  );\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-tabs\",\n    className: cn(\"ml-2 min-w-0 overflow-hidden\", className),\n    children: tabsElement,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ntype CodeBlockFloatingCopyProps = useRender.ComponentProps<\"div\">;\n\nfunction CodeBlockFloatingCopy({\n  className,\n  render,\n  ...props\n}: CodeBlockFloatingCopyProps) {\n  const context = useCodeBlock();\n  const code = context.code;\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-floating-copy\",\n    className: cn(\"absolute pointer-events-none z-1 top-2 right-2\", className),\n    children: (\n      <CopyButton\n        data-slot=\"code-block-floating-copy\"\n        content={code}\n        className=\"pointer-events-auto backdrop-blur-sm\"\n      />\n    ),\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ninterface CodeBlockPreProps extends useRender.ComponentProps<\"pre\"> {\n  /** Show line numbers in the gutter */\n  lineNumbers?: boolean;\n  /** Configure fade edges to indicate scrollable content. Defaults to true. */\n  fadeEdges?: FadeEdges;\n  /** Hide scrollbars while keeping scroll functionality */\n  hideScrollbar?: boolean;\n  /** Use native browser scrolling instead of Base UI ScrollArea */\n  nativeScroll?: boolean;\n}\n\nfunction CodeBlockPre({\n  className,\n  render,\n  children,\n  lineNumbers = false,\n  fadeEdges = true,\n  hideScrollbar = false,\n  nativeScroll = false,\n  ...props\n}: CodeBlockPreProps) {\n  const context = useCodeBlock();\n  const lines = context.lines;\n  const hasFocus = context.hasFocus;\n  const floatingCopy = context.floatingCopy;\n\n  const content =\n    lineNumbers && lines.length > 0 ? (\n      <div className=\"flex\">\n        <CodeBlockLineNumbers lines={lines} />\n        <div className=\"min-w-0 flex-1\">{children}</div>\n      </div>\n    ) : (\n      children\n    );\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-pre\",\n    \"data-has-focus\": hasFocus ? \"true\" : undefined,\n    className: cn(\n      \"relative rounded-lg whitespace-pre overflow-hidden max-h-96 flex flex-col\",\n      // Inner code body card sitting in the gray tray.\n      solidSurface(3),\n      // Focus mode: blur non-focused lines\n      \"[&[data-has-focus]_.line:not([data-focused])]:opacity-60 [&[data-has-focus]_.line:not([data-focused])]:blur-[1px] [&[data-has-focus]_.line:not([data-focused])]:transition-all\",\n      className,\n    ),\n    children: (\n      <>\n        {floatingCopy && <CodeBlockFloatingCopy />}\n        <ScrollArea\n          fadeEdges={fadeEdges}\n          hideScrollbar={hideScrollbar}\n          nativeScroll={nativeScroll}\n          viewportClassName=\"py-3\"\n          className=\"min-h-0 flex-1\"\n        >\n          {content}\n        </ScrollArea>\n      </>\n    ),\n  };\n\n  const element = useRender({\n    defaultTagName: \"pre\",\n    render,\n    props: mergeProps<\"pre\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ninterface CodeBlockLineNumbersProps extends useRender.ComponentProps<\"div\"> {\n  lines: string[];\n}\n\nfunction CodeBlockLineNumbers({\n  className,\n  render,\n  lines,\n  ...props\n}: CodeBlockLineNumbersProps) {\n  const lineNumbers = lines.map((_, index) => (\n    <div\n      key={index}\n      className=\"text-right text-[.8125rem] leading-normal tabular-nums\"\n    >\n      {index + 1}\n    </div>\n  ));\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-line-numbers\",\n    \"data-line-numbers\": \"true\",\n    className: cn(\"text-muted-foreground pl-3 select-none\", className),\n    children: lineNumbers,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\ntype CodeBlockCodeProps = useRender.ComponentProps<\"div\">;\n\nfunction CodeBlockCode({ className, render, ...props }: CodeBlockCodeProps) {\n  const context = useCodeBlock();\n  const code = context.code;\n  const nodes = context.nodes;\n  const showDiff = context.showDiff;\n\n  const prehighlightedCode = useMemo(() => {\n    const originalLines = code.split(\"\\n\");\n    let lines = [...originalLines];\n\n    if (showDiff) {\n      lines = lines.map((line) => stripDiffMarker(line));\n    }\n\n    // Mirror Shiki's output structure: `.line` spans separated by newline text nodes.\n    const elements: React.ReactNode[] = [];\n    lines.forEach((line, index) => {\n      elements.push(\n        <span key={`line-${index}`} className=\"line\">\n          {line || \" \"}\n        </span>,\n      );\n      if (index < lines.length - 1) {\n        elements.push(\"\\n\");\n      }\n    });\n    return <code>{elements}</code>;\n  }, [code, showDiff]);\n\n  const defaultProps = {\n    \"data-slot\": \"code-block-code\",\n    className: cn(\n      // font-mono on the wrapper plus the Shiki/fallback pre & code elements:\n      // the UA stylesheet sets `font-family: monospace` directly on pre/code,\n      // which beats inheritance, so Geist Mono must be applied to them too.\n      \"block font-mono text-[.8125rem] leading-normal whitespace-pre w-fit min-w-full [&_pre]:font-mono [&_code]:font-mono\",\n      // Uniform padding: raw text (no `.line`) vs. Shiki `.line` spans.\n      \"[&:not(:has(.line))]:px-3 [&_.line]:!px-3 [&:not(:has(.line))]:pr-8 [&_.line]:!pr-8\",\n      // Full-width lines so bg highlights stretch edge-to-edge.\n      \"[&_.line]:inline-block [&_.line]:min-w-full\",\n      // Left-border highlight: pl offset = px - border-width so text stays aligned.\n      \"[&_.line[data-highlighted]]:bg-primary/10 [&_.line[data-highlighted]]:border-l-2 [&_.line[data-highlighted]]:border-primary/50 [&_.line[data-highlighted]]:!pl-[calc(0.75rem-2px)]\",\n      // Diff: added lines\n      \"[&_.line[data-diff='added']]:bg-green-500/10 [&_.line[data-diff='added']]:border-l-2 [&_.line[data-diff='added']]:border-green-500/70 [&_.line[data-diff='added']]:!pl-[calc(0.75rem-2px)]\",\n      // Diff: removed lines\n      \"[&_.line[data-diff='removed']]:bg-red-500/10 [&_.line[data-diff='removed']]:border-l-2 [&_.line[data-diff='removed']]:border-red-500/70 [&_.line[data-diff='removed']]:!pl-[calc(0.75rem-2px)]\",\n      // Diff: modified lines\n      \"[&_.line[data-diff='modified']]:bg-yellow-500/10 [&_.line[data-diff='modified']]:border-l-2 [&_.line[data-diff='modified']]:border-yellow-500/70 [&_.line[data-diff='modified']]:!pl-[calc(0.75rem-2px)]\",\n      className,\n    ),\n    children: nodes || prehighlightedCode,\n  };\n\n  const element = useRender({\n    defaultTagName: \"div\",\n    render,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n\n  return element;\n}\n\nexport {\n  CodeBlock,\n  CodeBlockHeader,\n  CodeBlockFloatingCopy,\n  CodeBlockPre,\n  CodeBlockLineNumbers,\n  CodeBlockCode,\n  useCodeBlock,\n};\nexport type { FadeEdges };\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/code-block/code-block.tsx"
    },
    {
      "path": "registry/default/code-block/lib/shiki-shared.ts",
      "content": "import type { JSX } from \"react\";\nimport type { ShikiTransformer } from \"shiki/core\";\nimport type { BundledLanguage } from \"shiki/langs\";\nimport { toJsxRuntime } from \"hast-util-to-jsx-runtime\";\nimport { Fragment } from \"react\";\nimport { jsx, jsxs } from \"react/jsx-runtime\";\nimport { createHighlighterCore } from \"shiki/core\";\nimport { createJavaScriptRegexEngine } from \"shiki/engine/javascript\";\nimport { createHighlightLinesTransformer } from \"./transformers/highlight-lines\";\nimport { createDiffTransformer } from \"./transformers/diff\";\nimport { createFocusTransformer } from \"./transformers/focus\";\n\n// Global highlighter promise (per Shiki Next.js docs pattern)\n// Define without await as a global variable to reference from components\n// https://shiki.style/packages/next\nconst highlighter = createHighlighterCore({\n  themes: [\n    import(\"shiki/themes/github-light.mjs\"),\n    import(\"shiki/themes/github-dark.mjs\"),\n  ],\n  langs: [], // Load languages on-demand for optimal performance\n  engine: createJavaScriptRegexEngine(), // Smaller bundle, faster startup than WASM\n});\n\n// Track loaded languages to avoid redundant imports\nconst loadedLanguages = new Set<string>();\n\n// Shiki's built-in no-op grammars. These need no on-demand `shiki/langs/*`\n// import (there is no module for them) — `codeToHast` handles them directly.\n// Used for fenced blocks with no language (file trees, plain output) so they\n// still render in a real <pre> rather than collapsing into inline code.\nconst PLAINTEXT_LANGS = new Set([\"text\", \"txt\", \"plaintext\", \"plain\", \"ansi\"]);\n\nconst BASE_TRANSFORMERS: ShikiTransformer[] = [\n  {\n    name: \"remove-background\",\n    pre(node) {\n      delete node.properties.style;\n    },\n    code(node) {\n      delete node.properties.style;\n    },\n  },\n  {\n    name: \"fix-empty-lines\",\n    line(node) {\n      // Ensure empty lines have a space to maintain height\n      // Shiki creates empty .line spans for blank lines which collapse\n      if (!node.children || node.children.length === 0) {\n        node.children = [{ type: \"text\", value: \" \" }];\n      }\n    },\n  },\n];\n\nexport interface HighlightOptions {\n  highlightLines?: number[] | string;\n  showDiff?: boolean;\n  focusLines?: number[] | string;\n}\n\nasync function highlightWithLang(\n  code: string,\n  lang: BundledLanguage,\n  options?: HighlightOptions,\n) {\n  // Await the global highlighter promise (per Shiki Next.js docs)\n  const instance = await highlighter;\n\n  // Load language dynamically if not already loaded (cached Set for performance).\n  // Plaintext grammars are built in, so skip the import that would 404.\n  if (!PLAINTEXT_LANGS.has(lang) && !loadedLanguages.has(lang)) {\n    // Dynamically import only the specific language module needed\n    const langModule = await import(`shiki/langs/${lang}.mjs`);\n    await instance.loadLanguage(langModule.default);\n    loadedLanguages.add(lang);\n  }\n\n  // Build transformers array based on options\n  const transformers: ShikiTransformer[] = [...BASE_TRANSFORMERS];\n\n  if (options?.highlightLines) {\n    transformers.push(createHighlightLinesTransformer(options.highlightLines));\n  }\n\n  if (options?.showDiff) {\n    transformers.push(createDiffTransformer());\n  }\n\n  if (options?.focusLines) {\n    transformers.push(createFocusTransformer(options.focusLines));\n  }\n\n  const out = instance.codeToHast(code, {\n    lang,\n    themes: {\n      light: \"github-light\",\n      dark: \"github-dark\",\n    },\n    defaultColor: \"light-dark()\",\n    transformers,\n  });\n\n  return toJsxRuntime(out, { Fragment, jsx, jsxs }) as JSX.Element;\n}\n\nexport async function highlight(\n  code: string,\n  lang: BundledLanguage,\n  options?: HighlightOptions,\n) {\n  try {\n    return await highlightWithLang(code, lang, options);\n  } catch {\n    // If language isn't supported, try with javascript as fallback\n    try {\n      return await highlightWithLang(code, \"javascript\", options);\n    } catch {\n      // Final fallback to plain text\n      return jsx(\"pre\", { children: code }) as JSX.Element;\n    }\n  }\n}\n",
      "type": "registry:lib",
      "target": "components/ui/cubby-ui/code-block/lib/shiki-shared.ts"
    },
    {
      "path": "registry/default/code-block/lib/transformers/diff.ts",
      "content": "import type { ShikiTransformer } from \"shiki\";\nimport { detectDiffMarker, stripDiffMarker } from \"./utils\";\n\n/**\n * Shiki transformer that detects diff markers and adds data-diff attributes\n * Supports:\n * - \"+\" prefix for added lines\n * - \"-\" prefix for removed lines\n * - \"!\" prefix for modified lines\n * - Comment variants: \"// +\", \"// -\", \"// !\"\n *\n * The markers are stripped from the displayed code for clean output\n *\n * @returns Shiki transformer\n */\nexport function createDiffTransformer(): ShikiTransformer {\n  // Store original code lines for diff detection\n  let codeLines: string[] = [];\n\n  return {\n    name: \"diff\",\n    preprocess(code) {\n      // Store original lines before Shiki processes them\n      codeLines = code.split(\"\\n\");\n      // Strip diff markers from the code that will be highlighted\n      return code\n        .split(\"\\n\")\n        .map((line) => stripDiffMarker(line))\n        .join(\"\\n\");\n    },\n    line(node, lineNumber) {\n      // lineNumber is 1-indexed by Shiki, convert to 0-indexed for array access\n      const originalLine = codeLines[lineNumber - 1];\n      if (!originalLine) return;\n\n      const diffType = detectDiffMarker(originalLine);\n      if (diffType) {\n        node.properties[\"data-diff\"] = diffType;\n      }\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/ui/cubby-ui/code-block/lib/transformers/diff.ts"
    },
    {
      "path": "registry/default/code-block/lib/transformers/focus.ts",
      "content": "import type { ShikiTransformer } from \"shiki\";\nimport { parseLineRange } from \"./utils\";\n\n/**\n * Shiki transformer that adds focus attributes to lines\n * - Adds data-line-number to all lines for targeting\n * - Adds data-focused to specified lines\n *\n * @param lines - Line numbers to focus (1-indexed) or range string like \"1-3,5\"\n * @returns Shiki transformer\n */\nexport function createFocusTransformer(\n  lines: number[] | string | undefined,\n): ShikiTransformer {\n  const focusedLines = parseLineRange(lines);\n  const focusSet = new Set(focusedLines);\n\n  return {\n    name: \"focus\",\n    line(node, lineNumber) {\n      // lineNumber is already 1-indexed by Shiki\n      // Add line number to all lines for CSS targeting\n      node.properties[\"data-line-number\"] = String(lineNumber);\n\n      // Mark focused lines\n      if (focusSet.has(lineNumber)) {\n        node.properties[\"data-focused\"] = \"true\";\n      }\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/ui/cubby-ui/code-block/lib/transformers/focus.ts"
    },
    {
      "path": "registry/default/code-block/lib/transformers/highlight-lines.ts",
      "content": "import type { ShikiTransformer } from \"shiki\";\nimport { parseLineRange } from \"./utils\";\n\n/**\n * Shiki transformer that adds data-highlighted attribute to specified lines\n *\n * @param lines - Line numbers to highlight (1-indexed) or range string like \"1-3,5,7-9\"\n * @returns Shiki transformer\n */\nexport function createHighlightLinesTransformer(\n  lines: number[] | string | undefined,\n): ShikiTransformer {\n  const highlightedLines = parseLineRange(lines);\n  const highlightSet = new Set(highlightedLines);\n\n  return {\n    name: \"highlight-lines\",\n    line(node, lineNumber) {\n      // lineNumber is already 1-indexed by Shiki\n      if (highlightSet.has(lineNumber)) {\n        node.properties[\"data-highlighted\"] = \"true\";\n      }\n    },\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/ui/cubby-ui/code-block/lib/transformers/highlight-lines.ts"
    },
    {
      "path": "registry/default/code-block/lib/transformers/utils.ts",
      "content": "/**\n * Parse line range syntax into an array of line numbers\n * Supports formats:\n * - Array: [1, 2, 3]\n * - String: \"1,3,5\" or \"1-3,5,7-9\"\n * - Mixed: \"1-3,5\" becomes [1, 2, 3, 5]\n *\n * @param input - Line numbers as array or range string\n * @returns Array of line numbers (1-indexed)\n */\nexport function parseLineRange(\n  input: number[] | string | undefined,\n): number[] {\n  if (!input) return [];\n  if (Array.isArray(input)) return input;\n\n  const lines = new Set<number>();\n  const parts = input.split(\",\").map((s) => s.trim());\n\n  for (const part of parts) {\n    // Range format: \"1-3\"\n    if (part.includes(\"-\")) {\n      const [start, end] = part.split(\"-\").map((s) => Number.parseInt(s, 10));\n      if (!Number.isNaN(start) && !Number.isNaN(end)) {\n        for (let i = start; i <= end; i++) {\n          lines.add(i);\n        }\n      }\n    } else {\n      // Single number\n      const num = Number.parseInt(part, 10);\n      if (!Number.isNaN(num)) {\n        lines.add(num);\n      }\n    }\n  }\n\n  return Array.from(lines).sort((a, b) => a - b);\n}\n\n/**\n * Detect diff markers in code lines\n * Supports:\n * - Prefix markers: \"+\", \"-\", \"!\"\n * - Comment markers: \"// +\", \"// -\", \"// !\"\n *\n * @param line - Code line to check\n * @returns Diff type or null\n */\nexport function detectDiffMarker(\n  line: string,\n): \"added\" | \"removed\" | \"modified\" | null {\n  const trimmed = line.trim();\n\n  // Direct prefix markers\n  if (trimmed.startsWith(\"+\")) return \"added\";\n  if (trimmed.startsWith(\"-\")) return \"removed\";\n  if (trimmed.startsWith(\"!\")) return \"modified\";\n\n  // Comment markers (for languages that don't support bare +/-)\n  if (trimmed.startsWith(\"// +\")) return \"added\";\n  if (trimmed.startsWith(\"// -\")) return \"removed\";\n  if (trimmed.startsWith(\"// !\")) return \"modified\";\n\n  return null;\n}\n\n/**\n * Strip diff markers from a code line\n *\n * @param line - Code line with potential diff marker\n * @returns Line with marker removed\n */\nexport function stripDiffMarker(line: string): string {\n  const trimmed = line.trim();\n\n  // Direct prefix markers\n  if (trimmed.startsWith(\"+\") || trimmed.startsWith(\"-\") || trimmed.startsWith(\"!\")) {\n    return line.replace(/^(\\s*)[-+!](\\s?)/, \"$1\");\n  }\n\n  // Comment markers\n  if (trimmed.startsWith(\"// +\") || trimmed.startsWith(\"// -\") || trimmed.startsWith(\"// !\")) {\n    return line.replace(/^(\\s*)\\/\\/\\s*[-+!](\\s?)/, \"$1\");\n  }\n\n  return line;\n}\n",
      "type": "registry:lib",
      "target": "components/ui/cubby-ui/code-block/lib/transformers/utils.ts"
    }
  ],
  "type": "registry:ui"
}