{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "animated-number",
  "title": "Animated Number",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/animated-number/animated-number.component.ts",
      "content": "import {\n  Component,\n  Input,\n  OnChanges,\n  OnDestroy,\n  SimpleChanges,\n  booleanAttribute,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\n\nconst defaultFormat = (v: number) => String(Math.round(v))\n\n/** Inputs that restart the tween (React's effect dependency list). `format` / `class` do not. */\nconst TWEEN_INPUTS = ['value', 'from', 'duration', 'delay', 'disabled']\n\n/**\n * Tweened number display (React `AnimatedNumber`). Counts up from `from` on mount, then smoothly\n * retargets from the currently displayed value whenever `value` changes. Ease-out cubic via\n * requestAnimationFrame; `disabled` or prefers-reduced-motion render the target instantly.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-animated-number, [ui-animated-number]',\n  standalone: true,\n  host: {\n    'data-uipkge': '',\n    'data-slot': 'animated-number',\n    '[class]': 'hostClass',\n  },\n  template: `{{ formatted }}`,\n})\nexport class UiAnimatedNumberComponent implements OnChanges, OnDestroy {\n  @Input({ required: true }) value = 0\n  /** Value the first animation starts from. */\n  @Input() from = 0\n  /** ms per tween. */\n  @Input() duration = 900\n  /** ms before the first tween starts. */\n  @Input() delay = 0\n  @Input() format?: (value: number) => string\n  /** Render the target value instantly, no tween. */\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input('class') className?: string\n\n  // Signal-backed: written from rAF / timers, which never schedule change detection in zoneless apps.\n  private readonly display = signal(0)\n  private mounted = false\n  private frame = 0\n  private timer?: ReturnType<typeof setTimeout>\n\n  get hostClass(): string {\n    return cn('tabular-nums', this.className)\n  }\n\n  get formatted(): string {\n    return (this.format ?? defaultFormat)(this.display())\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (!this.mounted) this.display.set(this.value)\n    if (this.mounted && !TWEEN_INPUTS.some((k) => k in changes)) return\n    this.run()\n  }\n\n  ngOnDestroy(): void {\n    this.cancel()\n  }\n\n  private cancel(): void {\n    if (this.frame) cancelAnimationFrame(this.frame)\n    this.frame = 0\n    if (this.timer !== undefined) {\n      clearTimeout(this.timer)\n      this.timer = undefined\n    }\n  }\n\n  private run(): void {\n    this.cancel()\n    const value = this.value\n    const reduce = typeof window === 'undefined' || !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n    if (this.disabled || reduce) {\n      this.display.set(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.set(startValue)\n\n    const duration = this.duration\n    const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3)\n    const tween = () => {\n      const startTime = performance.now()\n      const step = () => {\n        const t = Math.min(1, (performance.now() - startTime) / duration)\n        this.display.set(startValue + (value - startValue) * easeOutCubic(t))\n        this.frame = t < 1 ? requestAnimationFrame(step) : 0\n      }\n      step()\n    }\n\n    if (firstRun && this.delay > 0) this.timer = setTimeout(tween, this.delay)\n    else tween()\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/animated-number/animated-number.component.ts"
    },
    {
      "path": "packages/registry-angular/components/animated-number/index.ts",
      "content": "export { UiAnimatedNumberComponent } from './animated-number.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/animated-number/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "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"
  ]
}