{
  "name": "lit-ui-code-block-view",
  "title": "lit-ui-code-block-view",
  "type": "registry:component",
  "description": "",
  "registryDependencies": [],
  "dependencies": [
    "beautiful-mermaid",
    "prosekit@^0.22.0"
  ],
  "files": [
    {
      "path": "registry/src/lit/ui/code-block-view/code-block-view.ts",
      "type": "registry:component",
      "target": "components/editor/ui/code-block-view/code-block-view.ts",
      "content": "import { renderMermaidSVG, THEMES } from 'beautiful-mermaid'\nimport { html, render } from 'lit'\nimport type { Extension } from 'prosekit/core'\nimport { defineNodeView } from 'prosekit/core'\nimport type { CodeBlockAttrs } from 'prosekit/extensions/code-block'\nimport {\n  isCodeBlockPreviewHiddenDecoration,\n  shikiBundledLanguagesInfo,\n} from 'prosekit/extensions/code-block'\nimport type { ProseMirrorNode } from 'prosekit/pm/model'\nimport { TextSelection } from 'prosekit/pm/state'\nimport type { Decoration, EditorView } from 'prosekit/pm/view'\n\nclass CodeBlockNodeView {\n  dom: HTMLElement\n  contentDOM: HTMLElement\n  private node: ProseMirrorNode\n  private view: EditorView\n  private getPos: () => number | undefined\n  private decorations: readonly Decoration[]\n  private wrapper: HTMLDivElement\n  private pre: HTMLPreElement\n  private preview: HTMLDivElement\n\n  constructor(\n    node: ProseMirrorNode,\n    view: EditorView,\n    getPos: () => number | undefined,\n    decorations: readonly Decoration[],\n  ) {\n    this.node = node\n    this.view = view\n    this.getPos = getPos\n    this.decorations = decorations\n\n    const root = document.createElement('div')\n    root.setAttribute('data-node-view-root', 'true')\n\n    this.wrapper = document.createElement('div')\n    this.wrapper.className = 'relative mx-2 top-3 h-0 select-none overflow-visible text-xs data-preview:hidden'\n    this.wrapper.setAttribute('contenteditable', 'false')\n\n    this.pre = document.createElement('pre')\n    this.pre.className = 'data-preview:hidden'\n    this.contentDOM = document.createElement('code')\n    this.contentDOM.setAttribute('data-node-view-content', 'true')\n    this.contentDOM.style.whiteSpace = 'inherit'\n    this.pre.appendChild(this.contentDOM)\n\n    this.preview = document.createElement('div')\n    this.preview.className = 'block py-2 overflow-auto'\n    this.preview.setAttribute('contenteditable', 'false')\n    this.preview.setAttribute('aria-label', 'Edit source')\n    this.preview.addEventListener('mousedown', this.handlePreviewMouseDown)\n\n    root.appendChild(this.wrapper)\n    root.appendChild(this.pre)\n\n    this.dom = root\n    this.sync()\n  }\n\n  private handleChange = (event: Event) => {\n    const language = (event.target as HTMLSelectElement).value\n    const pos = this.getPos()\n    if (typeof pos !== 'number') return\n    const attrs: CodeBlockAttrs = { ...(this.node.attrs as CodeBlockAttrs), language }\n    this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, undefined, attrs))\n  }\n\n  private handlePreviewMouseDown = (event: MouseEvent) => {\n    event.preventDefault()\n    const pos = this.getPos()\n    if (typeof pos !== 'number') return\n    const selection = TextSelection.near(this.view.state.doc.resolve(pos + 1), 1)\n    this.view.dispatch(this.view.state.tr.setSelection(selection))\n    this.view.focus()\n    this.pre.scrollIntoView({ behavior: 'smooth', block: 'nearest' })\n  }\n\n  private sync() {\n    const language = (this.node.attrs as CodeBlockAttrs).language || ''\n    const hidePreview = this.decorations.some(isCodeBlockPreviewHiddenDecoration)\n    const showMermaidPreview = !hidePreview && language === 'mermaid'\n\n    render(\n      html`\n        <select\n          aria-label=\"Code block language\"\n          class=\"outline-unset focus:outline-unset relative box-border w-auto cursor-pointer select-none appearance-none rounded-sm border-none bg-transparent px-2 py-1 text-xs transition text-(--prosemirror-highlight) opacity-0 hover:opacity-80 [div[data-node-view-root]:hover_&]:opacity-50 hover:[div[data-node-view-root]:hover_&]:opacity-80\"\n          .value=${language}\n          @change=${this.handleChange}\n        >\n          <option value=\"\">Plain Text</option>\n          ${shikiBundledLanguagesInfo.map(\n            (info) => html`<option value=${info.id}>${info.name}</option>`,\n          )}\n        </select>\n      `,\n      this.wrapper,\n    )\n\n    if (language) {\n      this.pre.setAttribute('data-language', language)\n    } else {\n      this.pre.removeAttribute('data-language')\n    }\n\n    if (showMermaidPreview) {\n      this.wrapper.setAttribute('data-preview', '')\n      this.pre.setAttribute('data-preview', '')\n      this.renderPreview()\n      if (!this.preview.isConnected) {\n        this.dom.appendChild(this.preview)\n      }\n    } else {\n      this.wrapper.removeAttribute('data-preview')\n      this.pre.removeAttribute('data-preview')\n      this.preview.replaceChildren()\n      if (this.preview.isConnected) {\n        this.preview.remove()\n      }\n    }\n  }\n\n  private renderPreview() {\n    this.preview.replaceChildren()\n    try {\n      const svg = renderMermaidSVG(this.node.textContent, THEMES['tokyo-night'])\n      const svgWrapper = document.createElement('div')\n      svgWrapper.innerHTML = svg\n      this.preview.appendChild(svgWrapper)\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err)\n      const errorPre = document.createElement('pre')\n      errorPre.textContent = message\n      this.preview.appendChild(errorPre)\n    }\n  }\n\n  update(node: ProseMirrorNode, decorations: readonly Decoration[]) {\n    if (node.type !== this.node.type) return false\n    this.node = node\n    this.decorations = decorations\n    this.sync()\n    return true\n  }\n\n  destroy() {\n    this.preview.removeEventListener('mousedown', this.handlePreviewMouseDown)\n    render(null, this.wrapper)\n  }\n}\n\nexport function defineCodeBlockView(): Extension {\n  return defineNodeView({\n    name: 'codeBlock',\n    constructor: (node, view, getPos, decorations) =>\n      new CodeBlockNodeView(node, view, getPos, decorations),\n  })\n}\n"
    },
    {
      "path": "registry/src/lit/ui/code-block-view/index.ts",
      "type": "registry:component",
      "target": "components/editor/ui/code-block-view/index.ts",
      "content": "export { defineCodeBlockView } from './code-block-view.ts'\n"
    }
  ],
  "meta": {
    "hasIcons": false,
    "hidden": false,
    "story": "",
    "framework": "lit",
    "accumulatedFiles": [
      "registry/src/lit/ui/code-block-view/code-block-view.ts",
      "registry/src/lit/ui/code-block-view/index.ts"
    ],
    "internalDependencies": []
  },
  "$schema": "https://ui.shadcn.com/schema/registry-item.json"
}
