{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "relative-time",
  "title": "Relative Time",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/relative-time/relative-time.component.ts",
      "content": "import {\n  Component,\n  Input,\n  type OnChanges,\n  type OnDestroy,\n  type OnInit,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  formatAbsoluteTime,\n  formatVisibleTime,\n  toDate,\n  type RelativeTimeDisplay,\n  type RelativeTimeNumeric,\n  type RelativeTimeParseAs,\n  type RelativeTimeStyle,\n} from './format-relative-time'\n\n/**\n * Angular port of UIPKGE RelativeTime (React `RelativeTime`). Put it on a `<time>` element\n * (`<time ui-relative-time [date]=\"…\">`) so the host IS the semantic `<time>` like React; the\n * `<ui-relative-time>` element form works too. Live relative label (\"2 minutes ago\") via\n * Intl.RelativeTimeFormat that ticks every `updateInterval` ms unless `now` is given (a\n * signal tick, so it repaints zoneless). `display` switches relative / absolute / both,\n * `timeZone` formats the clock + title tooltip, `parseAs` treats naive ISO strings as local\n * or UTC. Projected content replaces the label (React `children ?? label`).\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-relative-time, [ui-relative-time]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"relative-time\"',\n    '[attr.data-display]': 'display',\n    '[attr.data-timezone]': 'timeZone || \"local\"',\n    '[attr.data-parse-as]': 'parseAs',\n    '[attr.datetime]': 'isoString',\n    '[attr.title]': 'absolute',\n    '[class]': 'hostClass',\n  },\n  template: `<ng-content>{{ label }}</ng-content>`,\n})\nexport class UiRelativeTimeComponent implements OnInit, OnChanges, OnDestroy {\n  /** Instant to display. Accepts a Date, ISO string, or epoch ms. */\n  @Input({ required: true }) date!: Date | string | number\n  /** Clock used for the delta. Pass in tests and SSR to keep output stable. */\n  @Input() now?: Date | string | number\n  /** Intl relative style. */\n  @Input() formatStyle: RelativeTimeStyle = 'long'\n  /** `auto` yields \"yesterday\"; `always` yields \"1 day ago\". */\n  @Input() numeric: RelativeTimeNumeric = 'auto'\n  /** BCP 47 locale. Defaults to the runtime locale. */\n  @Input() locale?: string\n  /** Visible label: relative (default), absolute clock, or both. */\n  @Input() display: RelativeTimeDisplay = 'relative'\n  /** IANA zone for absolute text and the title tooltip. Omit for the browser local zone. */\n  @Input() timeZone?: string\n  /** How to parse date strings with no offset. */\n  @Input() parseAs: RelativeTimeParseAs = 'local'\n  /** Tick interval in ms. `0` freezes the clock. */\n  @Input() updateInterval = 30_000\n  @Input('class') className?: string\n\n  private timer: ReturnType<typeof setInterval> | null = null\n  private readonly _tick = signal(0)\n\n  /** Created once: a fresh Date on every read changed [attr.datetime] between checks (NG0100). */\n  private fallbackDate?: Date\n\n  get resolvedDate(): Date {\n    // `date` is required (as in React); the fallback only keeps a missing binding (e.g. data\n    // still loading) from throwing.\n    return toDate(this.date ?? (this.fallbackDate ??= new Date()), this.parseAs)\n  }\n\n  get resolvedNow(): Date {\n    // Reading the tick signal re-evaluates the label every updateInterval (zoneless-safe).\n    this._tick()\n    return this.now === undefined ? new Date() : toDate(this.now, this.parseAs)\n  }\n\n  get label(): string {\n    return formatVisibleTime(this.resolvedDate, this.resolvedNow, {\n      display: this.display,\n      style: this.formatStyle,\n      numeric: this.numeric,\n      locale: this.locale,\n      timeZone: this.timeZone,\n    })\n  }\n\n  get absolute(): string {\n    return formatAbsoluteTime(this.resolvedDate, this.locale, this.timeZone)\n  }\n\n  get isoString(): string {\n    return this.resolvedDate.toISOString()\n  }\n\n  get hostClass(): string {\n    return cn('text-muted-foreground text-sm tabular-nums', this.className)\n  }\n\n  private initialized = false\n\n  ngOnInit(): void {\n    this.initialized = true\n    this.startClock()\n  }\n\n  /** Restart the clock when updateInterval / now change (React effect deps). */\n  ngOnChanges(): void {\n    if (this.initialized) this.startClock()\n  }\n\n  private startClock(): void {\n    this.stopClock()\n    if (this.updateInterval <= 0 || this.now !== undefined) return\n    this.timer = setInterval(() => this._tick.update((t) => t + 1), this.updateInterval)\n  }\n\n  private stopClock(): void {\n    if (this.timer) clearInterval(this.timer)\n    this.timer = null\n  }\n\n  ngOnDestroy(): void {\n    this.stopClock()\n  }\n}\n\nexport {\n  formatAbsoluteTime,\n  formatRelativeTime,\n  formatVisibleTime,\n  toDate,\n  type RelativeTimeDisplay,\n  type RelativeTimeNumeric,\n  type RelativeTimeParseAs,\n  type RelativeTimeStyle,\n} from './format-relative-time'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/relative-time/relative-time.component.ts"
    },
    {
      "path": "packages/registry-angular/components/relative-time/format-relative-time.ts",
      "content": "export type RelativeTimeStyle = 'long' | 'short' | 'narrow'\nexport type RelativeTimeNumeric = 'always' | 'auto'\nexport type RelativeTimeParseAs = 'local' | 'utc'\nexport type RelativeTimeDisplay = 'relative' | 'absolute' | 'both'\n\nconst DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [\n  { amount: 60, unit: 'second' },\n  { amount: 60, unit: 'minute' },\n  { amount: 24, unit: 'hour' },\n  { amount: 7, unit: 'day' },\n  { amount: 4.34524, unit: 'week' },\n  { amount: 12, unit: 'month' },\n  { amount: Number.POSITIVE_INFINITY, unit: 'year' },\n]\n\nconst NAIVE_ISO = /^(\\d{4}-\\d{2}-\\d{2})(?:T(\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,3})?)?))?$/\n\nexport function toDate(value: Date | string | number, parseAs: RelativeTimeParseAs = 'local'): Date {\n  if (value instanceof Date) return value\n  if (typeof value === 'number') return new Date(value)\n  if (parseAs === 'utc') {\n    const naive = value.match(NAIVE_ISO)\n    if (naive) return new Date(`${naive[1]}T${naive[2] ?? '00:00:00.000'}Z`)\n  }\n  return new Date(value)\n}\n\nexport function formatRelativeTime(\n  date: Date,\n  now: Date,\n  options?: { style?: RelativeTimeStyle; numeric?: RelativeTimeNumeric; locale?: string },\n): string {\n  const rtf = new Intl.RelativeTimeFormat(options?.locale, {\n    numeric: options?.numeric ?? 'auto',\n    style: options?.style ?? 'long',\n  })\n  let duration = (date.getTime() - now.getTime()) / 1000\n  for (const division of DIVISIONS) {\n    if (Math.abs(duration) < division.amount) {\n      return rtf.format(Math.round(duration), division.unit)\n    }\n    duration /= division.amount\n  }\n  return rtf.format(0, 'second')\n}\n\nexport function formatAbsoluteTime(date: Date, locale?: string, timeZone?: string): string {\n  return new Intl.DateTimeFormat(locale, {\n    dateStyle: 'medium',\n    timeStyle: 'short',\n    timeZone,\n  }).format(date)\n}\n\nexport function formatVisibleTime(\n  date: Date,\n  now: Date,\n  options?: {\n    display?: RelativeTimeDisplay\n    style?: RelativeTimeStyle\n    numeric?: RelativeTimeNumeric\n    locale?: string\n    timeZone?: string\n  },\n): string {\n  const relative = formatRelativeTime(date, now, options)\n  if ((options?.display ?? 'relative') === 'relative') return relative\n  const absolute = formatAbsoluteTime(date, options?.locale, options?.timeZone)\n  if (options?.display === 'absolute') return absolute\n  return `${relative} · ${absolute}`\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/relative-time/format-relative-time.ts"
    },
    {
      "path": "packages/registry-angular/components/relative-time/index.ts",
      "content": "export {\n  UiRelativeTimeComponent,\n  formatAbsoluteTime,\n  formatRelativeTime,\n  formatVisibleTime,\n  toDate,\n  type RelativeTimeDisplay,\n  type RelativeTimeNumeric,\n  type RelativeTimeParseAs,\n  type RelativeTimeStyle,\n} from './relative-time.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/relative-time/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Live relative timestamp — \"2 minutes ago\", \"yesterday\", \"in 3 days\" — via Intl.RelativeTimeFormat. `display` switches the label to relative, absolute, or both. `timeZone` (IANA or `UTC`) formats the clock and tooltip; omit it for the browser local zone. `parseAs` treats naive ISO strings as local or UTC. Renders a semantic <time>. Pass `now` to freeze the clock for tests and SSR.",
  "categories": [
    "display",
    "utility"
  ]
}