{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "flight-radar-map",
  "title": "Flight Radar Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/flight-radar-map/flight-radar-map.component.ts",
      "content": "// Real-time commercial and cargo aviation radar with aircraft altitudes, ground speeds,\n// callsigns, and flight telemetry. Port of the React block 1:1 on Mapbox; markers follow\n// the hub-locator-map Angular pattern.\nimport {\n  type AfterViewInit,\n  ChangeDetectionStrategy,\n  Component,\n  type ElementRef,\n  Input,\n  type OnDestroy,\n  type OnInit,\n  type QueryList,\n  ViewChildren,\n  signal,\n} from '@angular/core'\nimport type * as mapboxgl from 'mapbox-gl'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiMapComponent } from '@/ui/map/map.component'\n\nexport interface Flight {\n  id: string\n  callsign: string\n  airline: string\n  origin: string\n  destination: string\n  coords: [number, number]\n  heading: number\n  altitude: number\n  speed: number\n  aircraft: string\n  status: 'Cruising' | 'Ascending' | 'Descending'\n}\n\nexport const DEFAULT_FLIGHTS: Flight[] = [\n  {\n    id: 'f-101',\n    callsign: 'UAL482',\n    airline: 'United Airlines',\n    origin: 'SFO',\n    destination: 'ORD',\n    coords: [-112.0, 39.5],\n    heading: 78,\n    altitude: 34000,\n    speed: 520,\n    aircraft: 'B787-9',\n    status: 'Cruising',\n  },\n  {\n    id: 'f-102',\n    callsign: 'DAL1209',\n    airline: 'Delta Air Lines',\n    origin: 'LAX',\n    destination: 'JFK',\n    coords: [-104.5, 36.2],\n    heading: 72,\n    altitude: 38000,\n    speed: 545,\n    aircraft: 'A350-900',\n    status: 'Cruising',\n  },\n  {\n    id: 'f-103',\n    callsign: 'AAL89',\n    airline: 'American Airlines',\n    origin: 'DFW',\n    destination: 'SEA',\n    coords: [-107.2, 42.1],\n    heading: 325,\n    altitude: 29000,\n    speed: 480,\n    aircraft: 'B737-MAX8',\n    status: 'Cruising',\n  },\n  {\n    id: 'f-104',\n    callsign: 'FDX14',\n    airline: 'FedEx Express',\n    origin: 'MEM',\n    destination: 'OAK',\n    coords: [-115.8, 38.0],\n    heading: 285,\n    altitude: 22000,\n    speed: 440,\n    aircraft: 'B777-F',\n    status: 'Descending',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-flight-radar-map, [ui-flight-radar-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiMapComponent,\n  ],\n  host: {\n    'data-slot': 'flight-radar-map',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div class=\"grid gap-4 lg:grid-cols-[1fr_320px]\">\n      <div class=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n        <div\n          ui-map\n          [accessToken]=\"accessToken ?? ''\"\n          variant=\"dark\"\n          [center]=\"activeFlight().coords\"\n          [zoom]=\"5.2\"\n          class=\"h-full w-full\"\n          (created)=\"onMapCreated($event)\"\n        >\n          <!-- Marker elements: handed to mapbox-gl Markers once the map exists, which moves the\n               inner #marker node onto the map; the outer node stays put for Angular's @for. -->\n          <div class=\"hidden\">\n            @for (f of flights; track f.id) {\n              <div>\n                <div #marker>\n                  <div class=\"group relative flex cursor-pointer flex-col items-center\" (click)=\"selectFlight(f)\">\n                    <div [class]=\"pinClass(f.id)\" [style.transform]=\"'rotate(' + f.heading + 'deg)'\">▲</div>\n                    <span [class]=\"callsignClass(f.id)\">{{ f.callsign }}</span>\n                  </div>\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n\n      <!-- Aviation Telemetry Radar Drawer -->\n      <div ui-card class=\"flex flex-col justify-between\">\n        <div ui-card-header>\n          <div class=\"flex items-center justify-between\">\n            <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ activeFlight().airline }}</span>\n            <span ui-badge class=\"border-info/20 bg-info/10 text-info\">{{ activeFlight().status }}</span>\n          </div>\n          <h3 ui-card-title class=\"mt-2 font-mono text-xl\">{{ activeFlight().callsign }}</h3>\n          <p ui-card-description class=\"flex items-center gap-2 font-mono text-xs\">\n            <span>{{ activeFlight().origin }}</span>\n            <span>✈ ─── ✈</span>\n            <span>{{ activeFlight().destination }}</span>\n          </p>\n        </div>\n        <div ui-card-content class=\"space-y-4\">\n          <div class=\"border-border bg-muted/50 grid grid-cols-2 gap-2 rounded-lg border p-2.5 font-mono text-xs\">\n            <div>\n              <div class=\"text-muted-foreground text-xs uppercase\">Altitude</div>\n              <div class=\"text-foreground text-sm font-bold\">{{ activeFlight().altitude.toLocaleString() }} ft</div>\n            </div>\n            <div>\n              <div class=\"text-muted-foreground text-xs uppercase\">Ground Speed</div>\n              <div class=\"text-foreground text-sm font-bold\">{{ activeFlight().speed }} kts</div>\n            </div>\n            <div class=\"mt-2\">\n              <div class=\"text-muted-foreground text-xs uppercase\">Heading</div>\n              <div class=\"text-foreground text-sm font-bold\">{{ activeFlight().heading }}°</div>\n            </div>\n            <div class=\"mt-2\">\n              <div class=\"text-muted-foreground text-xs uppercase\">Equipment</div>\n              <div class=\"text-foreground text-sm font-bold\">{{ activeFlight().aircraft }}</div>\n            </div>\n          </div>\n\n          <!-- Flight Selector List -->\n          <div class=\"border-border space-y-1 border-t pt-3\">\n            <div class=\"text-muted-foreground mb-1.5 font-mono text-xs tracking-wider uppercase\">\n              Monitored In-Flight\n            </div>\n            @for (f of flights; track f.id) {\n              <button type=\"button\" [class]=\"flightRowClass(f.id)\" (click)=\"selectFlight(f)\">\n                <span class=\"font-mono\">{{ f.callsign }} ({{ f.origin }}→{{ f.destination }})</span>\n                <span class=\"font-mono text-xs opacity-75\">{{ f.altitude / 1000 }}k ft</span>\n              </button>\n            }\n          </div>\n\n          <button ui-button variant=\"outline\" size=\"sm\" class=\"w-full font-mono text-xs\" (click)=\"recenter()\">\n            Recenter Camera\n          </button>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiFlightRadarMapComponent implements OnInit, AfterViewInit, OnDestroy {\n  @Input() flights: Flight[] = DEFAULT_FLIGHTS.map((f) => ({ ...f, coords: [...f.coords] as [number, number] }))\n  @Input() accessToken?: string\n  @Input('class') className?: string\n\n  @ViewChildren('marker') private markerEls?: QueryList<ElementRef<HTMLDivElement>>\n\n  readonly activeId = signal('')\n\n  private map: mapboxgl.Map | null = null\n  private markers: mapboxgl.Marker[] = []\n  private destroyed = false\n\n  get hostClass(): string {\n    return cn('block w-full', this.className)\n  }\n\n  ngOnInit(): void {\n    this.activeId.set(this.flights[0]?.id ?? '')\n  }\n\n  ngAfterViewInit(): void {\n    // A new flights list re-renders the marker elements; put the new set on the map.\n    this.markerEls?.changes.subscribe(() => void this.syncMarkers())\n  }\n\n  ngOnDestroy(): void {\n    this.destroyed = true\n    this.clearMarkers()\n    this.map = null\n  }\n\n  activeFlight(): Flight {\n    return this.flights.find((f) => f.id === this.activeId()) ?? this.flights[0]!\n  }\n\n  pinClass(id: string): string {\n    return cn(\n      'text-info flex size-7 items-center justify-center rounded-full font-bold',\n      this.activeId() === id ? 'bg-info/20 ring-2 ring-sky-400' : 'bg-background/80',\n    )\n  }\n\n  callsignClass(id: string): string {\n    return cn(\n      'border-border bg-card/90 mt-1 rounded border px-1 py-0.5 font-mono text-xs font-semibold tracking-wider whitespace-nowrap shadow-xs',\n      this.activeId() === id ? 'border-info/50 text-info' : 'text-foreground',\n    )\n  }\n\n  flightRowClass(id: string): string {\n    return cn(\n      'flex w-full items-center justify-between rounded-md px-2 py-1.5 text-xs transition-colors',\n      this.activeId() === id\n        ? 'bg-accent text-accent-foreground font-semibold'\n        : 'hover:bg-muted text-muted-foreground',\n    )\n  }\n\n  onMapCreated(map: mapboxgl.Map): void {\n    this.map = map\n    void this.syncMarkers()\n  }\n\n  selectFlight(f: Flight): void {\n    this.activeId.set(f.id)\n    this.map?.flyTo?.({ center: f.coords, zoom: 6.5 })\n  }\n\n  recenter(): void {\n    const active = this.activeFlight()\n    if (active) this.selectFlight(active)\n  }\n\n  /** One mapbox-gl Marker per flight, anchored at its centre (the React MapMarker, anchor=\"center\"). */\n  private async syncMarkers(): Promise<void> {\n    const map = this.map\n    const els = this.markerEls?.toArray() ?? []\n    if (!map) return\n    const mod = await import('mapbox-gl')\n    const mapbox = mod.default ?? mod\n    if (this.destroyed || map !== this.map) return\n    this.clearMarkers()\n    this.markers = els.flatMap((el, i) => {\n      const flight = this.flights[i]\n      if (!flight) return []\n      return [new mapbox.Marker({ element: el.nativeElement, anchor: 'center' }).setLngLat(flight.coords).addTo(map)]\n    })\n  }\n\n  private clearMarkers(): void {\n    for (const m of this.markers) m.remove()\n    this.markers = []\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/flight-radar-map/flight-radar-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/flight-radar-map/index.ts",
      "content": "export { UiFlightRadarMapComponent, DEFAULT_FLIGHTS, type Flight } from './flight-radar-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/flight-radar-map/index.ts"
    }
  ],
  "dependencies": [
    "mapbox-gl"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/map.json"
  ],
  "description": "Real-time commercial and cargo aviation radar with aircraft altitudes, ground speeds, callsigns, and flight telemetry.",
  "categories": [
    "logistics",
    "aviation",
    "map"
  ]
}