{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "countdown",
  "title": "Countdown",
  "type": "registry:ui",
  "description": "Countdown timer that shows time remaining to a target date. Digits flip on each tick. Supports DD:HH:MM:SS, HH:MM:SS, MM:SS, and SS formats plus custom token formats, render props for days/hours/minutes/seconds, a label, leading-zero padding, a custom separator, paused state, and on-finish/tick events.",
  "categories": [
    "display",
    "utility"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/countdown/countdown.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\nconst countdownStyles = css`\n  @keyframes countdown-digit-flip {\n    0% {\n      opacity: 0;\n      transform: translateY(45%) scale(0.92);\n    }\n    100% {\n      opacity: 1;\n      transform: translateY(0) scale(1);\n    }\n  }\n  .countdown-digit {\n    display: inline-block;\n    animation: countdown-digit-flip 280ms cubic-bezier(0.22, 1, 0.36, 1) both;\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .countdown-digit {\n      animation: none !important;\n    }\n  }\n`\n\nexport interface CountdownRenderProps {\n  days: number\n  hours: number\n  minutes: number\n  seconds: number\n  display: string\n  finished: boolean\n}\n\nfunction renderDigits(value: number, pad: boolean) {\n  const text = pad ? String(value).padStart(2, '0') : String(value)\n  return html`\n    ${[...text].map(\n      (ch, i) => html`\n        <span key=${`${i}-${ch}`} class=\"countdown-digit tabular-nums\">${ch}</span>\n      `,\n    )}\n  `\n}\n\n/**\n * <uip-countdown> — countdown timer to a target date or timestamp.\n */\nexport class UipCountdown extends LitElement {\n  static styles = [\n    tailwind,\n    countdownStyles,\n    css`\n      :host {\n        display: inline-flex;\n      }\n    `,\n  ]\n\n  static properties = {\n    target: { reflect: true },\n    format: { reflect: true },\n    paused: { type: Boolean, reflect: true },\n    label: { reflect: true },\n    pad: { type: Boolean, reflect: true },\n    separator: { reflect: true },\n    renderDays: { attribute: false },\n    renderHours: { attribute: false },\n    renderMinutes: { attribute: false },\n    renderSeconds: { attribute: false },\n    now: { state: true },\n    finished: { state: true },\n  }\n\n  target: Date | string | number = Date.now()\n  format = 'DD:HH:MM:SS'\n  paused = false\n  label = ''\n  pad = true\n  separator = ':'\n  renderDays?: (days: number) => unknown\n  renderHours?: (hours: number) => unknown\n  renderMinutes?: (minutes: number) => unknown\n  renderSeconds?: (seconds: number) => unknown\n\n  private now = Date.now()\n  private finished = false\n  private timer?: ReturnType<typeof setInterval>\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', 'countdown')\n    this.now = Date.now()\n    this.startTimer()\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    this.stopTimer()\n  }\n\n  private stopTimer() {\n    if (this.timer) {\n      clearInterval(this.timer)\n      this.timer = undefined\n    }\n  }\n\n  private startTimer() {\n    if (isServer) return\n    this.stopTimer()\n\n    const targetMs = this.getTargetMs()\n    const remainingNow = Math.max(0, targetMs - Date.now())\n    if (remainingNow <= 0) {\n      if (!this.finished) {\n        this.finished = true\n        this.dispatchEvent(new CustomEvent('finish', { bubbles: true, composed: true }))\n      }\n      return\n    }\n\n    if (this.paused) return\n\n    this.timer = setInterval(() => {\n      const next = Date.now()\n      const remaining = Math.max(0, targetMs - next)\n      this.now = next\n      this.dispatchEvent(new CustomEvent('tick', { detail: { remaining }, bubbles: true, composed: true }))\n      if (remaining <= 0) {\n        this.finished = true\n        this.dispatchEvent(new CustomEvent('finish', { bubbles: true, composed: true }))\n        this.stopTimer()\n      }\n    }, 1000)\n  }\n\n  protected updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps)\n    if (changedProps.has('target') || changedProps.has('paused')) {\n      if (changedProps.has('target')) {\n        this.finished = false\n        this.now = Date.now()\n      }\n      this.startTimer()\n    }\n    this.setAttribute('data-finished', String(this.finished))\n    this.setAttribute('data-paused', String(this.paused))\n  }\n\n  private getTargetMs(): number {\n    const t = this.target\n    if (t instanceof Date) return t.getTime()\n    if (typeof t === 'number') return t\n    const parsed = new Date(t).getTime()\n    return Number.isNaN(parsed) ? Date.now() : parsed\n  }\n\n  render() {\n    const targetMs = this.getTargetMs()\n    const remainingMs = Math.max(0, targetMs - this.now)\n\n    const total = remainingMs\n    const days = Math.floor(total / 86_400_000)\n    const hours = Math.floor((total % 86_400_000) / 3_600_000)\n    const minutes = Math.floor((total % 3_600_000) / 60_000)\n    const seconds = Math.floor((total % 60_000) / 1000)\n\n    let displayDays = days\n    let displayHours = hours\n    let displayMinutes = minutes\n    let displaySeconds = seconds\n\n    const f = this.format\n    if (f === 'HH:MM:SS') {\n      displayHours = days * 24 + hours\n    } else if (f === 'MM:SS') {\n      displayMinutes = days * 24 * 60 + hours * 60 + minutes\n    } else if (f === 'SS') {\n      displaySeconds = Math.floor(remainingMs / 1000)\n    }\n\n    const pad = this.pad\n    const sep = this.separator\n\n    return html`\n      <div\n        part=\"base\"\n        data-slot=\"countdown\"\n        data-finished=${this.finished ? 'true' : 'false'}\n        data-paused=${this.paused ? 'true' : 'false'}\n        class=\"inline-flex flex-col gap-1\"\n      >\n        ${this.label\n          ? html`\n              <span\n                data-slot=\"countdown-label\"\n                class=\"text-muted-foreground text-xs font-medium tracking-wide uppercase\"\n              >\n                ${this.label}\n              </span>\n            `\n          : nothing}\n        <div\n          data-slot=\"countdown-display\"\n          class=\"flex items-baseline gap-1 font-mono tabular-nums\"\n          role=\"timer\"\n          aria-live=${this.finished || this.paused ? 'off' : 'polite'}\n          aria-atomic=\"true\"\n        >\n          <slot>\n            ${f.includes('DD')\n              ? html`\n                  ${this.renderDays\n                    ? this.renderDays(days)\n                    : html`\n                        <span data-slot=\"countdown-days\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n                          ${renderDigits(displayDays, pad)}\n                        </span>\n                      `}\n                `\n              : nothing}\n            ${f.includes('DD') && f.includes('HH') ? html`<span class=\"text-muted-foreground text-2xl\">${sep}</span>` : nothing}\n            ${f.includes('HH')\n              ? html`\n                  ${this.renderHours\n                    ? this.renderHours(displayHours)\n                    : html`\n                        <span data-slot=\"countdown-hours\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n                          ${renderDigits(displayHours, pad)}\n                        </span>\n                      `}\n                `\n              : nothing}\n            ${f.includes('HH') && f.includes('MM') ? html`<span class=\"text-muted-foreground text-2xl\">${sep}</span>` : nothing}\n            ${f.includes('MM')\n              ? html`\n                  ${this.renderMinutes\n                    ? this.renderMinutes(displayMinutes)\n                    : html`\n                        <span data-slot=\"countdown-minutes\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n                          ${renderDigits(displayMinutes, pad)}\n                        </span>\n                      `}\n                `\n              : nothing}\n            ${f.includes('MM') && f.includes('SS') ? html`<span class=\"text-muted-foreground text-2xl\">${sep}</span>` : nothing}\n            ${f.includes('SS')\n              ? html`\n                  ${this.renderSeconds\n                    ? this.renderSeconds(displaySeconds)\n                    : html`\n                        <span data-slot=\"countdown-seconds\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n                          ${renderDigits(displaySeconds, pad)}\n                        </span>\n                      `}\n                `\n              : nothing}\n          </slot>\n        </div>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-countdown') || customElements.define('uip-countdown', UipCountdown)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-countdown': UipCountdown\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/countdown/countdown.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
