{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "payment-form",
  "title": "Payment Form",
  "type": "registry:block",
  "description": "Credit-card checkout form with a live 3D card preview, wallet shortcut buttons (Apple Pay / Google Pay / PayPal), brand auto-detection, Luhn validation, expiry / CVC checks, and a hookable async submit. Wallet buttons call `onWallet` so the consumer wires the actual SDK; the card form calls `onSuccess` / `onError` after the consumer-provided `onSubmit` settles.",
  "categories": [
    "commerce"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/payment-form/payment-form.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { Loader2 } from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/payment-card/payment-card'\nimport '../../components/input/input'\nimport type { UipInput } from '../../components/input/input'\nimport '../../components/label/label'\nimport '../../components/button/button'\n\ntype CardBrand = 'visa' | 'mastercard' | 'amex' | 'discover' | 'unknown'\ntype Wallet = 'apple' | 'google' | 'paypal'\n\nexport interface PaymentPayload {\n  number: string\n  name: string\n  expiry: string\n  cvc: string\n  brand: CardBrand\n}\n\nfunction detectBrand(raw: string): CardBrand {\n  const n = raw.replace(/\\D/g, '')\n  if (!n) return 'unknown'\n  if (/^4/.test(n)) return 'visa'\n  if (/^(5[1-5]|2[2-7])/.test(n)) return 'mastercard'\n  if (/^3[47]/.test(n)) return 'amex'\n  if (/^(6011|65|64[4-9])/.test(n)) return 'discover'\n  return 'unknown'\n}\n\nfunction luhn(digits: string): boolean {\n  let sum = 0\n  let alt = false\n  for (let i = digits.length - 1; i >= 0; i--) {\n    let d = Number(digits[i])\n    if (alt) {\n      d *= 2\n      if (d > 9) d -= 9\n    }\n    sum += d\n    alt = !alt\n  }\n  return sum % 10 === 0 && digits.length >= 13\n}\n\n// Wallet brand wordmark paths (from simpleicons.org, MIT-licensed) — verbatim from React.\nconst APPLE_PAY_PATH =\n  'M2.15 4.318a42.16 42.16 0 0 0-.454.003c-.15.005-.303.013-.452.04a1.44 1.44 0 0 0-1.06.772c-.07.138-.114.278-.14.43-.028.148-.037.3-.04.45A10.2 10.2 0 0 0 0 6.222v11.557c0 .07.002.138.003.207.004.15.013.303.04.452.027.15.072.291.142.429a1.436 1.436 0 0 0 .63.63c.138.07.278.115.43.142.148.027.3.036.45.04l.208.003h20.194l.207-.003c.15-.004.303-.013.452-.04.15-.027.291-.071.428-.141a1.432 1.432 0 0 0 .631-.631c.07-.138.115-.278.141-.43.027-.148.036-.3.04-.45.002-.07.003-.138.003-.208l.001-.246V6.221c0-.07-.002-.138-.004-.207a2.995 2.995 0 0 0-.04-.452 1.446 1.446 0 0 0-1.2-1.201 3.022 3.022 0 0 0-.452-.04 10.448 10.448 0 0 0-.453-.003zm0 .512h19.942c.066 0 .131.002.197.003.115.004.25.01.375.032.109.02.2.05.287.094a.927.927 0 0 1 .407.407.997.997 0 0 1 .094.288c.022.123.028.258.031.374.002.065.003.13.003.197v11.552c0 .065 0 .13-.003.196-.003.115-.009.25-.032.375a.927.927 0 0 1-.5.693 1.002 1.002 0 0 1-.286.094 2.598 2.598 0 0 1-.373.032l-.2.003H1.906c-.066 0-.133-.002-.196-.003a2.61 2.61 0 0 1-.375-.032c-.109-.02-.2-.05-.288-.094a.918.918 0 0 1-.406-.407 1.006 1.006 0 0 1-.094-.288 2.531 2.531 0 0 1-.032-.373 9.588 9.588 0 0 1-.002-.197V6.224c0-.065 0-.131.002-.197.004-.114.01-.248.032-.375.02-.108.05-.199.094-.287a.925.925 0 0 1 .407-.406 1.03 1.03 0 0 1 .287-.094c.125-.022.26-.029.375-.032.065-.002.131-.002.196-.003zm4.71 3.7c-.3.016-.668.199-.88.456-.191.22-.36.58-.316.918.338.03.675-.169.888-.418.205-.258.345-.603.308-.955zm2.207.42v5.493h.852v-1.877h1.18c1.078 0 1.835-.739 1.835-1.812 0-1.07-.742-1.805-1.808-1.805zm.852.719h.982c.739 0 1.161.396 1.161 1.089 0 .692-.422 1.092-1.164 1.092h-.979zm-3.154.3c-.45.01-.83.28-1.05.28-.235 0-.593-.264-.981-.257a1.446 1.446 0 0 0-1.23.747c-.527.908-.139 2.255.374 2.995.249.366.549.769.944.754.373-.014.52-.242.973-.242.454 0 .586.242.98.235.41-.007.667-.366.915-.733.286-.417.403-.82.41-.841-.007-.008-.79-.308-.797-1.209-.008-.754.615-1.113.644-1.135-.352-.52-.9-.578-1.09-.593a1.123 1.123 0 0 0-.092-.002zm8.204.397c-.99 0-1.606.533-1.652 1.256h.777c.072-.358.369-.586.845-.586.502 0 .803.266.803.711v.309l-1.097.064c-.951.054-1.488.484-1.488 1.184 0 .72.548 1.207 1.332 1.207.526 0 1.032-.281 1.264-.727h.019v.659h.788v-2.76c0-.803-.62-1.317-1.591-1.317zm1.94.072l1.446 4.009c0 .003-.073.24-.073.247-.125.41-.33.571-.711.571-.069 0-.206 0-.267-.015v.666c.06.011.267.019.335.019.83 0 1.226-.312 1.568-1.283l1.5-4.214h-.868l-1.012 3.259h-.015l-1.013-3.26zm-1.167 2.189v.316c0 .521-.45.917-1.024.917-.442 0-.731-.228-.731-.579 0-.342.278-.56.769-.593z'\nconst GOOGLE_PAY_PATH =\n  'M3.963 7.235A3.963 3.963 0 00.422 9.419a3.963 3.963 0 000 3.559 3.963 3.963 0 003.541 2.184c1.07 0 1.97-.352 2.627-.957.748-.69 1.18-1.71 1.18-2.916a4.722 4.722 0 00-.07-.806H3.964v1.526h2.14a1.835 1.835 0 01-.79 1.205c-.356.241-.814.379-1.35.379-1.034 0-1.911-.697-2.225-1.636a2.375 2.375 0 010-1.517c.314-.94 1.191-1.636 2.225-1.636a2.152 2.152 0 011.52.594l1.132-1.13a3.808 3.808 0 00-2.652-1.033zm6.501.55v6.9h.886V11.89h1.465c.603 0 1.11-.196 1.522-.588a1.911 1.911 0 00.635-1.464 1.92 1.92 0 00-.635-1.456 2.125 2.125 0 00-1.522-.598zm2.427.85a1.156 1.156 0 01.823.365 1.176 1.176 0 010 1.686 1.171 1.171 0 01-.877.357H11.35V8.635h1.487a1.156 1.156 0 01.054 0zm4.124 1.175c-.842 0-1.477.308-1.907.925l.781.491c.288-.417.68-.626 1.175-.626a1.255 1.255 0 01.856.323 1.009 1.009 0 01.366.785v.202c-.34-.193-.774-.289-1.3-.289-.617 0-1.11.145-1.479.434-.37.288-.554.677-.554 1.165a1.476 1.476 0 00.525 1.156c.35.308.785.463 1.305.463.61 0 1.098-.27 1.465-.81h.038v.655h.848v-2.909c0-.61-.19-1.09-.568-1.44-.38-.35-.896-.525-1.551-.525zm2.263.154l1.946 4.422-1.098 2.38h.915L24 9.963h-.965l-1.368 3.391h-.02l-1.406-3.39zm-2.146 2.368c.494 0 .88.11 1.156.33 0 .372-.147.696-.44.973a1.413 1.413 0 01-.997.414 1.081 1.081 0 01-.69-.232.708.708 0 01-.293-.578c0-.257.12-.47.363-.647.24-.173.54-.26.9-.26Z'\nconst PAYPAL_PATH =\n  'M15.607 4.653H8.941L6.645 19.251H1.82L4.862 0h7.995c3.754 0 6.375 2.294 6.473 5.513-.648-.478-2.105-.86-3.722-.86m6.57 5.546c0 3.41-3.01 6.853-6.958 6.853h-2.493L11.595 24H6.74l1.845-11.538h3.592c4.208 0 7.346-3.634 7.153-6.949a5.24 5.24 0 0 1 2.848 4.686M9.653 5.546h6.408c.907 0 1.942.222 2.363.541-.195 2.741-2.655 5.483-6.441 5.483H8.714Z'\n\n/**\n * <uip-payment-form> — the registry PaymentForm block as a web component.\n *\n * Class strings, wallet wordmarks, validation (Luhn, MM/YY expiry, CVC\n * length), formatting, and the shake keyframes are React's verbatim (the\n * keyframes are the one allowed extra-CSS exception). React state becomes\n * { state: true } fields. `amount` / `currency` / `show-wallets` are\n * attributes; `wallets` is a property (attribute: false — set it from JS).\n * `onSubmit` (payload) / `onSuccess` / `onError` (err) / `onBrandChange`\n * (brand) / `onWallet` (kind) become `submit` / `success` / `error` /\n * `brand-change` / `wallet` CustomEvents (bubbles, composed; payloads in\n * `detail`). Like login-01, the block simulates the processor round-trip\n * itself (1.2s) and then notifies; `simulate-error` forces the failure path\n * (shake + `error`) so the demo can show it without a callback prop.\n */\nexport class UipPaymentForm extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class; the shake keyframes are React's\n  // injected <style> copied verbatim (allowed keyframes exception).\n  static styles = [\n    tailwind,\n    css`\n      :host {\n        display: block;\n      }\n      @keyframes payment-form-shake {\n        0%,\n        100% {\n          transform: translateX(0);\n        }\n        20% {\n          transform: translateX(-6px);\n        }\n        40% {\n          transform: translateX(6px);\n        }\n        60% {\n          transform: translateX(-4px);\n        }\n        80% {\n          transform: translateX(2px);\n        }\n      }\n      .payment-form-shake {\n        animation: payment-form-shake 320ms cubic-bezier(0.36, 0.07, 0.19, 0.97);\n      }\n      @media (prefers-reduced-motion: reduce) {\n        .payment-form-shake {\n          animation: none;\n        }\n      }\n    `,\n  ]\n\n  static properties = {\n    amount: { type: Number },\n    currency: {},\n    showWallets: {\n      type: Boolean,\n      attribute: 'show-wallets',\n      converter: { fromAttribute: (v: string | null) => v !== null && v !== 'false' },\n    },\n    wallets: { attribute: false },\n    simulateError: { type: Boolean, attribute: 'simulate-error' },\n    cardNumber: { state: true },\n    cardName: { state: true },\n    expiry: { state: true },\n    cvc: { state: true },\n    cvcFocused: { state: true },\n    submitting: { state: true },\n    succeeded: { state: true },\n    errors: { state: true },\n    shakeKey: { state: true },\n  }\n\n  amount = 0\n  currency = 'USD'\n  showWallets = true\n  wallets: Wallet[] = ['apple', 'google', 'paypal']\n  simulateError = false\n\n  private cardNumber = ''\n  private cardName = ''\n  private expiry = ''\n  private cvc = ''\n  private cvcFocused = false\n  private submitting = false\n  private succeeded = false\n  private errors: Record<string, string | null> = { number: null, name: null, expiry: null, cvc: null }\n  private shakeKey = 0\n  private lastBrand: CardBrand = 'unknown'\n  private timer?: number\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', 'payment-form')\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (this.timer) window.clearTimeout(this.timer)\n  }\n\n  private get brand(): CardBrand {\n    return detectBrand(this.cardNumber)\n  }\n\n  private maybeEmitBrand(next: CardBrand) {\n    if (next !== this.lastBrand) {\n      this.lastBrand = next\n      this.dispatchEvent(new CustomEvent('brand-change', { detail: next, bubbles: true, composed: true }))\n    }\n  }\n\n  private formatNumber(raw: string, b: CardBrand): string {\n    const digits = raw.replace(/\\D/g, '').slice(0, b === 'amex' ? 15 : 16)\n    if (b === 'amex') {\n      return digits.replace(/^(\\d{0,4})(\\d{0,6})(\\d{0,5}).*$/, (_m, a, bb, c) => [a, bb, c].filter(Boolean).join(' '))\n    }\n    return digits.replace(/(.{4})/g, '$1 ').trim()\n  }\n\n  private formatExpiry(raw: string): string {\n    const d = raw.replace(/\\D/g, '').slice(0, 4)\n    if (d.length <= 2) return d\n    return `${d.slice(0, 2)}/${d.slice(2)}`\n  }\n\n  private validate(): boolean {\n    const digits = this.cardNumber.replace(/\\D/g, '')\n    const expectedLen = this.brand === 'amex' ? 15 : 16\n    const next: Record<string, string | null> = { number: null, name: null, expiry: null, cvc: null }\n\n    next.number = !digits\n      ? 'Card number required'\n      : digits.length !== expectedLen\n        ? `Card number must be ${expectedLen} digits`\n        : !luhn(digits)\n          ? 'Card number is invalid'\n          : null\n\n    next.name = !this.cardName.trim() ? 'Name required' : this.cardName.trim().length < 2 ? 'Name too short' : null\n\n    const expOk = /^(0[1-9]|1[0-2])\\/\\d{2}$/.test(this.expiry)\n    if (!expOk) {\n      next.expiry = 'Use MM/YY'\n    } else {\n      const [m, y] = this.expiry.split('/').map(Number)\n      const now = new Date()\n      const expDate = new Date(2000 + (y ?? 0), (m ?? 1) - 1, 1)\n      expDate.setMonth(expDate.getMonth() + 1)\n      next.expiry = expDate <= now ? 'Card has expired' : null\n    }\n\n    const cvcLen = this.brand === 'amex' ? 4 : 3\n    next.cvc = this.cvc.length !== cvcLen ? `CVC must be ${cvcLen} digits` : null\n\n    this.errors = next\n    const ok = Object.values(next).every((e) => !e)\n    if (!ok) this.shakeKey += 1\n    return ok\n  }\n\n  private handleSubmit(e: Event) {\n    e.preventDefault()\n    if (this.submitting || this.succeeded) return\n    if (!this.validate()) return\n    this.submitting = true\n    const payload: PaymentPayload = {\n      number: this.cardNumber.replace(/\\D/g, ''),\n      name: this.cardName.trim(),\n      expiry: this.expiry,\n      cvc: this.cvc,\n      brand: this.brand,\n    }\n    this.timer = window.setTimeout(() => {\n      this.submitting = false\n      if (this.simulateError) {\n        this.dispatchEvent(\n          new CustomEvent('error', {\n            detail: new Error('Card declined. Try a different payment method.'),\n            bubbles: true,\n            composed: true,\n          }),\n        )\n        this.shakeKey += 1\n        return\n      }\n      this.succeeded = true\n      this.dispatchEvent(new CustomEvent('submit', { detail: payload, bubbles: true, composed: true }))\n      this.dispatchEvent(new CustomEvent('success', { detail: payload, bubbles: true, composed: true }))\n    }, 1200)\n  }\n\n  private get formattedAmount() {\n    try {\n      return new Intl.NumberFormat(undefined, { style: 'currency', currency: this.currency }).format(this.amount)\n    } catch {\n      return `${this.currency} ${this.amount.toFixed(2)}`\n    }\n  }\n\n  private get walletList(): Wallet[] {\n    return (this.wallets ?? []).filter((w) => ['apple', 'google', 'paypal'].includes(w))\n  }\n\n  render() {\n    const walletList = this.walletList\n    const gridCols = walletList.length === 1 ? '' : walletList.length === 2 ? 'grid-cols-2' : 'grid-cols-3'\n    return html`\n      <div data-slot=\"payment-form\" class=\"bg-card text-card-foreground mx-auto w-full max-w-md rounded-xl border p-6 shadow-sm\">\n        <div class=\"mb-4 flex items-baseline justify-between\">\n          <h3 class=\"text-lg font-semibold\">Pay ${this.formattedAmount}</h3>\n          <span class=\"text-muted-foreground text-xs tracking-wider uppercase\">Secure checkout</span>\n        </div>\n\n        ${this.showWallets && walletList.length > 0\n          ? html`<div class=\"mb-4 space-y-3\">\n              <div class=${['grid gap-2', gridCols].filter(Boolean).join(' ')}>\n                ${walletList.includes('apple')\n                  ? html`<button\n                      type=\"button\"\n                      aria-label=\"Pay with Apple Pay\"\n                      class=\"flex h-11 items-center justify-center rounded-md bg-black px-3 text-white transition-transform hover:scale-[1.02] active:scale-100\"\n                      @click=${() =>\n                        this.dispatchEvent(new CustomEvent('wallet', { detail: 'apple', bubbles: true, composed: true }))}\n                    >\n                      <svg viewBox=\"0 0 24 24\" class=\"h-7 w-auto\" fill=\"currentColor\" aria-hidden=\"true\">\n                        <path d=${APPLE_PAY_PATH} />\n                      </svg>\n                    </button>`\n                  : nothing}\n                ${walletList.includes('google')\n                  ? html`<button\n                      type=\"button\"\n                      aria-label=\"Pay with Google Pay\"\n                      class=\"flex h-11 items-center justify-center rounded-md border bg-white px-3 text-slate-800 shadow-sm transition-transform hover:scale-[1.02] active:scale-100\"\n                      @click=${() =>\n                        this.dispatchEvent(\n                          new CustomEvent('wallet', { detail: 'google', bubbles: true, composed: true }),\n                        )}\n                    >\n                      <svg viewBox=\"0 0 24 24\" class=\"h-6 w-auto\" fill=\"currentColor\" aria-hidden=\"true\">\n                        <path d=${GOOGLE_PAY_PATH} />\n                      </svg>\n                    </button>`\n                  : nothing}\n                ${walletList.includes('paypal')\n                  ? html`<button\n                      type=\"button\"\n                      aria-label=\"Pay with PayPal\"\n                      class=\"flex h-11 items-center justify-center rounded-md bg-[#003087] px-3 text-white transition-transform hover:scale-[1.02] active:scale-100\"\n                      @click=${() =>\n                        this.dispatchEvent(\n                          new CustomEvent('wallet', { detail: 'paypal', bubbles: true, composed: true }),\n                        )}\n                    >\n                      <svg viewBox=\"0 0 24 24\" class=\"h-6 w-auto\" fill=\"currentColor\" aria-hidden=\"true\">\n                        <path d=${PAYPAL_PATH} />\n                      </svg>\n                    </button>`\n                  : nothing}\n              </div>\n              <div class=\"flex items-center gap-3\">\n                <div class=\"bg-border h-px flex-1\"></div>\n                <span class=\"text-muted-foreground text-xs tracking-widest uppercase\">or pay with card</span>\n                <div class=\"bg-border h-px flex-1\"></div>\n              </div>\n            </div>`\n          : nothing}\n\n        <div aria-hidden=\"true\" class=\"mb-5 flex justify-center\">\n          <uip-payment-card\n            number=${this.cardNumber}\n            name=${this.cardName}\n            expiry=${this.expiry}\n            cvc=${this.cvc}\n            ?flipped=${this.cvcFocused}\n            size=\"md\"\n          ></uip-payment-card>\n        </div>\n\n        <form class=\"space-y-3\" data-shake=${this.shakeKey} @submit=${(e: Event) => this.handleSubmit(e)}>\n          <div class=\"grid gap-1.5\">\n            <uip-label for=\"cc-number\">Card number</uip-label>\n            <uip-input\n              id=\"cc-number\"\n              type=\"text\"\n              autocomplete=\"cc-number\"\n              placeholder=\"1234 5678 9012 3456\"\n              .value=${this.cardNumber}\n              aria-invalid=${this.errors.number ? 'true' : nothing}\n              aria-describedby=\"cc-number-err\"\n              status=${this.errors.number ? 'error' : nothing}\n              class=${this.errors.number ? 'payment-form-shake' : ''}\n              @input=${(e: Event) => {\n                const el = e.target as UipInput\n                const nextBrand = detectBrand(el.value ?? '')\n                this.cardNumber = this.formatNumber(el.value ?? '', nextBrand)\n                this.errors = { ...this.errors, number: null }\n                this.maybeEmitBrand(nextBrand)\n              }}\n            ></uip-input>\n            ${this.errors.number\n              ? html`<p id=\"cc-number-err\" class=\"text-destructive text-xs\" role=\"alert\">${this.errors.number}</p>`\n              : nothing}\n          </div>\n\n          <div class=\"grid gap-1.5\">\n            <uip-label for=\"cc-name\">Name on card</uip-label>\n            <uip-input\n              id=\"cc-name\"\n              type=\"text\"\n              autocomplete=\"cc-name\"\n              placeholder=\"Jane Doe\"\n              .value=${this.cardName}\n              aria-invalid=${this.errors.name ? 'true' : nothing}\n              aria-describedby=\"cc-name-err\"\n              status=${this.errors.name ? 'error' : nothing}\n              class=${this.errors.name ? 'payment-form-shake' : ''}\n              @input=${(e: Event) => {\n                this.cardName = (e.target as UipInput).value ?? ''\n              }}\n            ></uip-input>\n            ${this.errors.name\n              ? html`<p id=\"cc-name-err\" class=\"text-destructive text-xs\" role=\"alert\">${this.errors.name}</p>`\n              : nothing}\n          </div>\n\n          <div class=\"grid grid-cols-2 gap-3\">\n            <div class=\"grid gap-1.5\">\n              <uip-label for=\"cc-exp\">Expiry</uip-label>\n              <uip-input\n                id=\"cc-exp\"\n                type=\"text\"\n                autocomplete=\"cc-exp\"\n                placeholder=\"MM/YY\"\n                .value=${this.expiry}\n                aria-invalid=${this.errors.expiry ? 'true' : nothing}\n                aria-describedby=\"cc-exp-err\"\n                status=${this.errors.expiry ? 'error' : nothing}\n                class=${this.errors.expiry ? 'payment-form-shake' : ''}\n                @input=${(e: Event) => {\n                  this.expiry = this.formatExpiry((e.target as UipInput).value ?? '')\n                  this.errors = { ...this.errors, expiry: null }\n                }}\n              ></uip-input>\n              ${this.errors.expiry\n                ? html`<p id=\"cc-exp-err\" class=\"text-destructive text-xs\" role=\"alert\">${this.errors.expiry}</p>`\n                : nothing}\n            </div>\n            <div class=\"grid gap-1.5\">\n              <uip-label for=\"cc-cvc\">CVC</uip-label>\n              <uip-input\n                id=\"cc-cvc\"\n                type=\"text\"\n                autocomplete=\"cc-csc\"\n                placeholder=${this.brand === 'amex' ? '1234' : '123'}\n                .value=${this.cvc}\n                aria-invalid=${this.errors.cvc ? 'true' : nothing}\n                aria-describedby=\"cc-cvc-err\"\n                status=${this.errors.cvc ? 'error' : nothing}\n                class=${this.errors.cvc ? 'payment-form-shake' : ''}\n                @input=${(e: Event) => {\n                  const max = this.brand === 'amex' ? 4 : 3\n                  this.cvc = ((e.target as UipInput).value ?? '').replace(/\\D/g, '').slice(0, max)\n                  this.errors = { ...this.errors, cvc: null }\n                }}\n                @focus=${() => {\n                  this.cvcFocused = true\n                }}\n                @blur=${() => {\n                  this.cvcFocused = false\n                }}\n              ></uip-input>\n              ${this.errors.cvc\n                ? html`<p id=\"cc-cvc-err\" class=\"text-destructive text-xs\" role=\"alert\">${this.errors.cvc}</p>`\n                : nothing}\n            </div>\n          </div>\n\n          <uip-button type=\"submit\" class=\"mt-2 h-11 w-full text-base\" ?disabled=${this.submitting || this.succeeded}>\n            ${this.submitting ? icon(Loader2, 'loader-circle', 'mr-2 size-4 animate-spin') : nothing}\n            ${this.succeeded ? html`<span>Paid ✓</span>` : this.submitting ? html`<span>Processing…</span>` : html`<span>Pay ${this.formattedAmount}</span>`}\n          </uip-button>\n        </form>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-payment-form') || customElements.define('uip-payment-form', UipPaymentForm)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-payment-form': UipPaymentForm\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/payment-form/payment-form.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/input.json",
    "https://uipkge.dev/r/lit/label.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/payment-card.json"
  ]
}
