{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "clipboard",
  "title": "Clipboard",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/clipboard/clipboard.component.ts",
      "content": "import {\n  Component,\n  ContentChild,\n  Directive,\n  type EmbeddedViewRef,\n  EventEmitter,\n  Input,\n  type OnChanges,\n  type OnDestroy,\n  Output,\n  TemplateRef,\n  ViewContainerRef,\n  booleanAttribute,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  UiTooltipComponent,\n  UiTooltipContentComponent,\n  UiTooltipProviderComponent,\n  UiTooltipTriggerComponent,\n} from '@/ui/tooltip/tooltip.component'\n\nexport type ClipboardState = 'idle' | 'success' | 'error'\n\n/** Template context for the render-prop form: `<ng-template let-state>`. */\nexport interface ClipboardTemplateContext {\n  $implicit: ClipboardState\n  state: ClipboardState\n}\n\n/** Renders the consumer's `<ng-template let-state>` with the live copy state (React render-prop children). */\n@Directive({ selector: '[uiClipboardOutlet]', standalone: true })\nexport class UiClipboardOutletDirective implements OnChanges, OnDestroy {\n  @Input('uiClipboardOutlet') template: TemplateRef<ClipboardTemplateContext> | null = null\n  @Input('uiClipboardOutletState') state: ClipboardState = 'idle'\n  private readonly vcr = inject(ViewContainerRef)\n  private view?: EmbeddedViewRef<ClipboardTemplateContext>\n  private tpl: TemplateRef<ClipboardTemplateContext> | null = null\n\n  ngOnChanges(): void {\n    if (this.template !== this.tpl) {\n      this.vcr.clear()\n      this.tpl = this.template\n      this.view = this.template\n        ? this.vcr.createEmbeddedView(this.template, { $implicit: this.state, state: this.state })\n        : undefined\n      return\n    }\n    if (this.view) {\n      this.view.context.$implicit = this.state\n      this.view.context.state = this.state\n      this.view.markForCheck()\n    }\n  }\n\n  ngOnDestroy(): void {\n    this.vcr.clear()\n  }\n}\n\n/**\n * Angular port of UIPKGE Clipboard (React `Clipboard`): a native `<button>` that copies `text`\n * (navigator.clipboard, execCommand fallback), swaps the Copy icon for a green Check on\n * success, and wraps itself in a Tooltip (300ms delay) whose text follows the state\n * (tooltip / successText / errorText) for `timeout` ms. `feedbackTooltip=false` hides the\n * tooltip while feedback is showing. Projected content, or an `<ng-template let-state>`\n * (React render-prop children), renders after the icon + label.\n *\n * Outputs: React `onCopy` is `copyText` here (`copy` is a native DOM event that would\n * collide), `success`, `error`.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-clipboard, [ui-clipboard]',\n  standalone: true,\n  imports: [\n    UiTooltipProviderComponent,\n    UiTooltipComponent,\n    UiTooltipTriggerComponent,\n    UiTooltipContentComponent,\n    UiClipboardOutletDirective,\n  ],\n  // React renders the button itself (Tooltip parts add no DOM).\n  host: { '[class]': '\"contents\"' },\n  template: `\n    <ui-tooltip-provider [delayDuration]=\"300\">\n      <ui-tooltip>\n        <button\n          ui-tooltip-trigger\n          type=\"button\"\n          data-uipkge=\"\"\n          data-slot=\"clipboard\"\n          [attr.data-feedback-state]=\"state()\"\n          [disabled]=\"disabled\"\n          [attr.aria-label]=\"currentTooltip\"\n          [class]=\"buttonClass\"\n          (click)=\"doCopy()\"\n        >\n          @if (!hideIcon) {\n            <span data-slot=\"clipboard-icon\" class=\"inline-flex\">\n              @if (state() === 'success') {\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  class=\"lucide lucide-check size-4 text-emerald-500\"\n                  aria-hidden=\"true\"\n                >\n                  <path d=\"M20 6 9 17l-5-5\" />\n                </svg>\n              } @else {\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  class=\"lucide lucide-copy size-4\"\n                  aria-hidden=\"true\"\n                >\n                  <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n                  <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n                </svg>\n              }\n            </span>\n          }\n          @if (label) {\n            <span data-slot=\"clipboard-label\">{{ label }}</span>\n          }\n          @if (children) {\n            <ng-container [uiClipboardOutlet]=\"children\" [uiClipboardOutletState]=\"state()\" />\n          }\n          <ng-content />\n        </button>\n        @if (feedbackTooltip || state() === 'idle') {\n          <ui-tooltip-content>{{ currentTooltip }}</ui-tooltip-content>\n        }\n      </ui-tooltip>\n    </ui-tooltip-provider>\n  `,\n})\nexport class UiClipboardComponent implements OnDestroy {\n  /** Text to copy to the clipboard. */\n  @Input() text = ''\n  /** Optional visible label next to the icon. */\n  @Input() label = ''\n  /** Disable the button (no copy, no tooltip). */\n  @Input({ transform: booleanAttribute }) disabled = false\n  /** Hide the copy icon (useful when a label is shown). */\n  @Input({ transform: booleanAttribute }) hideIcon = false\n  /** Tooltip text shown on hover before copying. */\n  @Input() tooltip = 'Copy'\n  /** Feedback text shown after a successful copy. */\n  @Input() successText = 'Copied!'\n  /** Feedback text shown after a failed copy. */\n  @Input() errorText = 'Failed'\n  /** How long (ms) the success/error feedback stays before resetting. */\n  @Input() timeout = 2000\n  /** Show the feedback as a tooltip rather than only swapping the icon. */\n  @Input({ transform: booleanAttribute }) feedbackTooltip = true\n  @Input('class') className?: string\n\n  /** Fired before the copy attempt with the text being copied (React `onCopy`). */\n  @Output() copyText = new EventEmitter<string>()\n  /** Fired after a successful copy. */\n  @Output() success = new EventEmitter<string>()\n  /** Fired after a failed copy. */\n  @Output() error = new EventEmitter<Error>()\n\n  /** Render-prop children: `<ng-template let-state>…</ng-template>`. */\n  @ContentChild(TemplateRef) children?: TemplateRef<ClipboardTemplateContext>\n\n  /** Signal so the timer-driven reset repaints in zoneless apps. */\n  readonly state = signal<ClipboardState>('idle')\n  private resetTimer: ReturnType<typeof setTimeout> | null = null\n\n  get buttonClass(): string {\n    return cn(\n      'inline-flex items-center gap-2 rounded-md text-sm transition-colors',\n      'text-muted-foreground hover:text-foreground',\n      'focus-visible:ring-ring/50 outline-none focus-visible:ring-[3px]',\n      'disabled:cursor-not-allowed disabled:opacity-50',\n      this.className,\n    )\n  }\n\n  get currentTooltip(): string {\n    const s = this.state()\n    return s === 'success' ? this.successText : s === 'error' ? this.errorText : this.tooltip\n  }\n\n  private setFeedback(next: ClipboardState): void {\n    this.state.set(next)\n    if (this.resetTimer) clearTimeout(this.resetTimer)\n    this.resetTimer = setTimeout(() => this.state.set('idle'), this.timeout)\n  }\n\n  async doCopy(): Promise<void> {\n    if (this.disabled) return\n    const value = this.text\n    this.copyText.emit(value)\n    try {\n      if (navigator.clipboard?.writeText) {\n        await navigator.clipboard.writeText(value)\n      } else {\n        // Legacy fallback for non-secure contexts.\n        const ta = document.createElement('textarea')\n        ta.value = value\n        ta.style.position = 'fixed'\n        ta.style.opacity = '0'\n        document.body.appendChild(ta)\n        ta.select()\n        const ok = document.execCommand('copy')\n        document.body.removeChild(ta)\n        if (!ok) throw new Error('execCommand copy failed')\n      }\n      this.setFeedback('success')\n      this.success.emit(value)\n    } catch (err) {\n      this.setFeedback('error')\n      this.error.emit(err as Error)\n    }\n  }\n\n  ngOnDestroy(): void {\n    if (this.resetTimer) clearTimeout(this.resetTimer)\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/clipboard/clipboard.component.ts"
    },
    {
      "path": "packages/registry-angular/components/clipboard/index.ts",
      "content": "export { UiClipboardComponent, UiClipboardOutletDirective } from './clipboard.component'\nexport type { ClipboardState, ClipboardTemplateContext } from './clipboard.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/clipboard/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/tooltip.json"
  ],
  "description": "Copy-to-clipboard button with success/error feedback. Swaps the icon to a check on success, shows a tooltip with configurable feedback text, supports a visible label, disabled state, a custom timeout for feedback reset, and copy/success/error events. Includes a legacy execCommand fallback for non-secure contexts. Composes the tooltip primitive.",
  "categories": [
    "utility",
    "control"
  ]
}