{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "animated-number",
  "title": "Animated Number",
  "type": "registry:ui",
  "description": "Tweened number display — counts up on mount and smoothly retargets when the value changes. Ease-out cubic via requestAnimationFrame, custom formatter (Intl-ready), delay/stagger support, honors prefers-reduced-motion, SSR-safe.",
  "categories": [
    "display",
    "data"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/animated-number/animated-number.ts",
      "content": "import { LitElement, css, html, isServer } from 'lit'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nconst defaultFormat = (v: number) => String(Math.round(v))\n\n/**\n * <uip-animated-number> — tweened number display.\n *\n * Counts up on mount, then smoothly retargets from the currently displayed\n * value whenever `value` changes. Server render outputs the final value so\n * hydration always matches.\n */\nexport class UipAnimatedNumber extends LitElement {\n  static styles = [tailwind, css`:host { display: inline; }`]\n\n  static properties = {\n    value: { type: Number },\n    from: { type: Number },\n    duration: { type: Number },\n    delay: { type: Number },\n    disabled: { type: Boolean },\n    format: { attribute: false },\n    display: { state: true },\n  }\n\n  value = 0\n  from = 0\n  duration = 900\n  delay = 0\n  disabled = false\n  format?: (value: number) => string\n  private display = 0\n\n  private mounted = false\n  private frame = 0\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', 'animated-number')\n    if (isServer) {\n      this.display = this.value\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    this.cancel()\n  }\n\n  private cancel() {\n    if (this.frame) {\n      cancelAnimationFrame(this.frame)\n      this.frame = 0\n    }\n    if (this.timer !== undefined) {\n      clearTimeout(this.timer)\n      this.timer = undefined\n    }\n  }\n\n  protected updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps)\n    if (isServer) return\n\n    if (changedProps.has('value') || changedProps.has('from') || changedProps.has('disabled')) {\n      this.cancel()\n\n      const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches\n      if (this.disabled || reduce) {\n        this.display = this.value\n        return\n      }\n\n      const firstRun = !this.mounted\n      this.mounted = true\n      const startValue = firstRun ? this.from : this.display\n      this.display = startValue\n\n      const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)\n\n      const run = () => {\n        const startTime = performance.now()\n        const duration = this.duration\n        const targetValue = this.value\n        const step = () => {\n          const t = Math.min(1, (performance.now() - startTime) / duration)\n          this.display = startValue + (targetValue - startValue) * easeOutCubic(t)\n          if (t < 1) {\n            this.frame = requestAnimationFrame(step)\n          } else {\n            this.frame = 0\n          }\n        }\n        step()\n      }\n\n      if (firstRun && this.delay > 0) {\n        this.timer = window.setTimeout(run, this.delay)\n      } else {\n        run()\n      }\n    }\n  }\n\n  render() {\n    const fmt = this.format ?? defaultFormat\n    return html`<span part=\"base\" data-slot=\"animated-number\" class=\"tabular-nums\">${fmt(this.display)}</span>`\n  }\n}\n\ncustomElements.get('uip-animated-number') || customElements.define('uip-animated-number', UipAnimatedNumber)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-animated-number': UipAnimatedNumber\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/animated-number/animated-number.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
