{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "signature-pad",
  "title": "Signature Pad",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/signature-pad/SignaturePad.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { Eraser } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { signaturePadVariants } from './signature-pad.variants'\n\ninterface Props {\n  modelValue?: string | null\n  width?: number\n  height?: number\n  penColor?: string\n  penThickness?: number\n  backgroundColor?: string\n  exportFormat?: string\n  disabled?: boolean\n  readonly?: boolean\n  showClearButton?: boolean\n  clearLabel?: string\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  modelValue: null,\n  width: 400,\n  height: 200,\n  penColor: '#0a0a0a',\n  penThickness: 2,\n  backgroundColor: '#ffffff',\n  exportFormat: 'image/png',\n  disabled: false,\n  readonly: false,\n  showClearButton: true,\n  clearLabel: 'Clear',\n})\n\nconst emit = defineEmits<{\n  'update:modelValue': [value: string | null]\n  begin: []\n  end: []\n  change: [value: string | null]\n}>()\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nconst isDrawing = ref(false)\nconst pointCount = ref(0)\nconst hasInk = ref(false)\nconst ctx = ref<CanvasRenderingContext2D | null>(null)\nlet lastX = 0\nlet lastY = 0\n\nconst isInteractive = computed(() => !props.disabled && !props.readonly)\n\nfunction setupCanvas() {\n  const canvas = canvasRef.value\n  if (!canvas) return\n  const dpr = window.devicePixelRatio || 1\n  canvas.width = props.width * dpr\n  canvas.height = props.height * dpr\n  canvas.style.width = `${props.width}px`\n  canvas.style.height = `${props.height}px`\n  const context = canvas.getContext('2d')\n  if (!context) return\n  context.scale(dpr, dpr)\n  context.lineCap = 'round'\n  context.lineJoin = 'round'\n  context.strokeStyle = props.penColor\n  context.lineWidth = props.penThickness\n  context.fillStyle = props.backgroundColor\n  context.fillRect(0, 0, props.width, props.height)\n  ctx.value = context\n  pointCount.value = 0\n  hasInk.value = false\n}\n\nfunction getPointerPos(e: PointerEvent): { x: number; y: number } {\n  const canvas = canvasRef.value\n  if (!canvas) return { x: 0, y: 0 }\n  const rect = canvas.getBoundingClientRect()\n  return {\n    x: e.clientX - rect.left,\n    y: e.clientY - rect.top,\n  }\n}\n\nfunction startDraw(e: PointerEvent) {\n  if (!isInteractive.value || !ctx.value) return\n  e.preventDefault()\n  isDrawing.value = true\n  const { x, y } = getPointerPos(e)\n  lastX = x\n  lastY = y\n  ctx.value.beginPath()\n  ctx.value.moveTo(x, y)\n  pointCount.value += 1\n  hasInk.value = true\n  emit('begin')\n  canvasRef.value?.setPointerCapture(e.pointerId)\n}\n\nfunction draw(e: PointerEvent) {\n  if (!isDrawing.value || !ctx.value) return\n  e.preventDefault()\n  const { x, y } = getPointerPos(e)\n  ctx.value.beginPath()\n  ctx.value.moveTo(lastX, lastY)\n  ctx.value.lineTo(x, y)\n  ctx.value.stroke()\n  lastX = x\n  lastY = y\n  pointCount.value += 1\n}\n\nfunction endDraw(e: PointerEvent) {\n  if (!isDrawing.value) return\n  isDrawing.value = false\n  if (ctx.value) ctx.value.closePath()\n  canvasRef.value?.releasePointerCapture(e.pointerId)\n  exportSignature()\n  emit('end')\n}\n\nfunction exportSignature() {\n  const canvas = canvasRef.value\n  if (!canvas) return\n  if (!hasInk.value) {\n    emit('update:modelValue', null)\n    emit('change', null)\n    return\n  }\n  const dataUrl = canvas.toDataURL(props.exportFormat)\n  emit('update:modelValue', dataUrl)\n  emit('change', dataUrl)\n}\n\nfunction clear() {\n  const canvas = canvasRef.value\n  const context = ctx.value\n  if (!canvas || !context) return\n  context.fillStyle = props.backgroundColor\n  context.fillRect(0, 0, props.width, props.height)\n  pointCount.value = 0\n  hasInk.value = false\n  emit('update:modelValue', null)\n  emit('change', null)\n}\n\ndefineExpose({\n  clear,\n  exportSignature,\n  toDataURL: exportSignature,\n  pointCount: computed(() => pointCount.value),\n  isEmpty: computed(() => !hasInk.value),\n})\n\nonMounted(() => {\n  setupCanvas()\n})\n\nonBeforeUnmount(() => {\n  ctx.value = null\n})\n\nwatch(\n  () => [props.penColor, props.penThickness, props.backgroundColor, props.width, props.height],\n  () => {\n    setupCanvas()\n  },\n)\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"signature-pad\"\n    :data-disabled=\"disabled ? '' : undefined\"\n    :data-readonly=\"readonly ? '' : undefined\"\n    :class=\"cn(signaturePadVariants(), props.class)\"\n  >\n    <canvas\n      ref=\"canvasRef\"\n      :class=\"cn('block touch-none rounded-md', !isInteractive && 'pointer-events-none')\"\n      :style=\"{ touchAction: 'none' }\"\n      :aria-label=\"'Signature pad' + (disabled ? ' (disabled)' : '')\"\n      role=\"img\"\n      @pointerdown=\"startDraw\"\n      @pointermove=\"draw\"\n      @pointerup=\"endDraw\"\n      @pointercancel=\"endDraw\"\n      @pointerleave=\"endDraw\"\n    />\n    <div v-if=\"showClearButton && isInteractive\" class=\"flex items-center justify-between gap-2 pt-2\">\n      <span class=\"text-muted-foreground text-xs tabular-nums\">{{ pointCount }} points</span>\n      <button\n        type=\"button\"\n        class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex items-center gap-1 rounded-md text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50\"\n        :disabled=\"!hasInk\"\n        @click=\"clear\"\n      >\n        <Eraser class=\"size-3.5\" />\n        {{ clearLabel }}\n      </button>\n    </div>\n    <slot name=\"actions\" :clear=\"clear\" :export=\"exportSignature\" :empty=\"!hasInk\" />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/signature-pad/SignaturePad.vue"
    },
    {
      "path": "packages/registry-vue/components/signature-pad/signature-pad.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\nexport const signaturePadVariants = cva(\n  'border-input bg-background inline-flex flex-col gap-1 rounded-lg border p-2 shadow-xs',\n)\n\nexport type SignaturePadVariants = VariantProps<typeof signaturePadVariants>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/signature-pad/signature-pad.variants.ts"
    },
    {
      "path": "packages/registry-vue/components/signature-pad/index.ts",
      "content": "export { default as SignaturePad } from './SignaturePad.vue'\nexport { signaturePadVariants, type SignaturePadVariants } from './signature-pad.variants'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/signature-pad/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Canvas-based digital signature capture with pointer events (mouse + touch). v-model emits a PNG data URL, with pen color/thickness, background, clear method, disabled/readonly states, and a live point count.",
  "categories": [
    "control",
    "form"
  ]
}