{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "terminal",
  "title": "Terminal",
  "type": "registry:ui",
  "description": "Terminal/command-line display with a macOS-style title bar, command history, prompt character, dark/light themes, auto-scroll, optional typing animation, and a line slot for custom formatting.",
  "categories": [
    "display"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/terminal/terminal.ts",
      "content": "import { LitElement, css, html, isServer, nothing } from 'lit'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nexport interface TerminalLine {\n  prompt?: string\n  command?: string\n  output?: string\n  type?: 'command' | 'output'\n}\n\nconst trueByDefault = {\n  fromAttribute: (v: string | null) => v !== 'false' && v !== null,\n  toAttribute: (v: boolean) => (v ? '' : 'false'),\n}\n\n/**\n * <uip-terminal> — simulated terminal window.\n */\nexport class UipTerminal extends LitElement {\n  static styles = [\n    tailwind,\n    css`\n      :host {\n        display: block;\n        width: 100%;\n      }\n    `,\n  ]\n\n  static properties = {\n    lines: { attribute: false },\n    title: { reflect: true },\n    promptChar: { attribute: 'prompt-char', reflect: true },\n    theme: { reflect: true },\n    autoScroll: { attribute: 'auto-scroll', converter: trueByDefault, reflect: true },\n    typing: { type: Boolean, reflect: true },\n    typingSpeed: { type: Number, attribute: 'typing-speed', reflect: true },\n    maxHeight: { attribute: 'max-height', reflect: true },\n    visibleCount: { state: true },\n  }\n\n  lines: TerminalLine[] = []\n  override title = 'bash'\n  promptChar = '$'\n  theme: 'dark' | 'light' = 'dark'\n  autoScroll = true\n  typing = false\n  typingSpeed = 120\n  maxHeight = '400px'\n\n  private visibleCount = 0\n  private typingTimer?: ReturnType<typeof setTimeout>\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'terminal')\n    this.visibleCount = this.typing ? 0 : this.lines.length\n    this.startTypingIfNeeded()\n    if (!this.typing) {\n      requestAnimationFrame(() => this.scrollToBottom())\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    this.stopTyping()\n  }\n\n  willUpdate() {\n    this.setAttribute('data-theme', this.theme)\n  }\n\n  protected updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps)\n    if (changedProps.has('lines') || changedProps.has('typing')) {\n      if (this.typing) {\n        this.visibleCount = 0\n      } else {\n        this.visibleCount = this.lines.length\n        requestAnimationFrame(() => this.scrollToBottom())\n      }\n      this.startTypingIfNeeded()\n    }\n  }\n\n  private stopTyping() {\n    if (this.typingTimer) {\n      clearTimeout(this.typingTimer)\n      this.typingTimer = undefined\n    }\n  }\n\n  private startTypingIfNeeded() {\n    if (isServer || !this.typing) return\n    this.stopTyping()\n    if (this.visibleCount >= this.lines.length) return\n\n    this.typingTimer = setTimeout(() => {\n      this.visibleCount++\n      this.scrollToBottom()\n      this.startTypingIfNeeded()\n    }, this.typingSpeed)\n  }\n\n  private scrollToBottom() {\n    if (!this.autoScroll) return\n    const body = this.renderRoot.querySelector<HTMLElement>('[data-slot=terminal-body]')\n    if (body) {\n      body.scrollTop = body.scrollHeight\n    }\n  }\n\n  render() {\n    const resolvedLines = this.lines.map((l) => ({\n      ...l,\n      type: l.type ?? (l.prompt || l.command ? 'command' : 'output'),\n      prompt: l.prompt ?? (l.type === 'output' ? '' : this.promptChar),\n    }))\n\n    const shownLines = resolvedLines.slice(0, this.visibleCount)\n\n    return html`\n      <div\n        part=\"base\"\n        data-slot=\"terminal\"\n        data-theme=${this.theme}\n        class=${cn(\n          'relative overflow-hidden rounded-lg border font-mono text-sm shadow-sm',\n          this.theme === 'dark' ? 'bg-card text-card-foreground border-border' : 'bg-muted border-border',\n        )}\n      >\n        <!-- Title bar -->\n        <div class=\"border-border bg-muted flex items-center gap-2 border-b px-4 py-2.5\">\n          <div class=\"flex gap-1.5\">\n            <span class=\"bg-destructive size-3 rounded-full\"></span>\n            <span class=\"bg-warning size-3 rounded-full\"></span>\n            <span class=\"bg-success size-3 rounded-full\"></span>\n          </div>\n          <span class=\"text-muted-foreground ml-2 text-xs\">${this.title}</span>\n        </div>\n\n        <!-- Body -->\n        <div\n          data-slot=\"terminal-body\"\n          class=\"overflow-auto p-4 leading-relaxed\"\n          style=\"max-height: ${this.maxHeight};\"\n        >\n          ${shownLines.map(\n            (line) => html`\n              <div data-slot=\"terminal-line\" class=\"break-words whitespace-pre-wrap\">\n                ${line.type === 'command'\n                  ? html`\n                      <div data-slot=\"terminal-command\" class=\"flex flex-wrap items-baseline gap-x-1.5\">\n                        <span class=\"text-success shrink-0 font-semibold\">${line.prompt}</span>\n                        <span>${line.command}</span>\n                      </div>\n                    `\n                  : nothing}\n                ${line.output\n                  ? html`\n                      <div data-slot=\"terminal-output\" class=\"text-muted-foreground\">\n                        ${line.output}\n                      </div>\n                    `\n                  : nothing}\n              </div>\n            `,\n          )}\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-terminal') || customElements.define('uip-terminal', UipTerminal)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-terminal': UipTerminal\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/terminal/terminal.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
