{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "vector-map",
  "title": "Vector Map",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/charts/vector-map/vector-map.component.ts",
      "content": "import {\n  Component,\n  EventEmitter,\n  Input,\n  OnChanges,\n  Output,\n  SimpleChanges,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  UiMapComponent,\n  UiMapSourceComponent,\n  UiMapLayerComponent,\n  UiMapMarkerComponent,\n} from '@/ui/map/map.component'\n\nexport interface MapRegion {\n  id: string\n  name: string\n  path?: string\n}\n\nexport interface MapPin {\n  id?: string\n  lat?: number\n  lng?: number\n  x?: number\n  y?: number\n  label?: string\n  value?: string | number\n  color?: string\n  status?: string\n  description?: string\n}\n\nexport interface FlowRoute {\n  id?: string\n  from: { lat?: number; lng?: number; x?: number; y?: number }\n  to: { lat?: number; lng?: number; x?: number; y?: number }\n  color?: string\n  width?: number\n  curvature?: number\n  animated?: boolean\n  dashed?: boolean\n  duration?: number\n  label?: string\n}\n\nexport interface RegionDataRecord {\n  value?: number\n  label?: string\n  status?: string\n  color?: string\n  description?: string\n}\n\nexport const WORLD_REGIONS: MapRegion[] = [\n  { id: 'northAmerica', name: 'North America' },\n  { id: 'southAmerica', name: 'South America' },\n  { id: 'europe', name: 'Europe' },\n  { id: 'asia', name: 'Asia' },\n  { id: 'africa', name: 'Africa' },\n  { id: 'australiaOceania', name: 'Oceania' },\n  { id: 'unitedKingdom', name: 'United Kingdom' },\n  { id: 'japan', name: 'Japan' },\n]\n\nexport const WORLD_COUNTRIES: MapRegion[] = [\n  { id: 'US', name: 'United States' },\n  { id: 'CA', name: 'Canada' },\n  { id: 'GB', name: 'United Kingdom' },\n  { id: 'DE', name: 'Germany' },\n  { id: 'FR', name: 'France' },\n  { id: 'JP', name: 'Japan' },\n  { id: 'CN', name: 'China' },\n  { id: 'IN', name: 'India' },\n  { id: 'BR', name: 'Brazil' },\n  { id: 'AU', name: 'Australia' },\n]\n\nexport function projectPoint(pt: { lat?: number; lng?: number; x?: number; y?: number }): { x: number; y: number } {\n  if (pt.x !== undefined && pt.y !== undefined) return { x: pt.x, y: pt.y }\n  const lng = pt.lng ?? 0\n  const lat = pt.lat ?? 0\n  const x = ((lng + 180) / 360) * 1000\n  const y = ((90 - lat) / 180) * 500\n  return { x, y }\n}\n\nconst PIN_FALLBACK = 'oklch(0.65 0.20 145)'\nconst ROUTE_FALLBACK = 'rgba(56, 189, 248, 0.75)'\n\nexport function buildRoutesGeoJson(routes: FlowRoute[]): unknown | null {\n  if (!routes || !routes.length) return null\n  return {\n    type: 'FeatureCollection',\n    features: routes.map((r, i) => {\n      const fromLng = r.from.lng ?? -74\n      const fromLat = r.from.lat ?? 40\n      const toLng = r.to.lng ?? 8\n      const toLat = r.to.lat ?? 50\n      return {\n        type: 'Feature',\n        id: i,\n        properties: { color: r.color || ROUTE_FALLBACK },\n        geometry: {\n          type: 'LineString',\n          coordinates: [\n            [fromLng, fromLat],\n            [(fromLng + toLng) / 2, (fromLat + toLat) / 2 + 5],\n            [toLng, toLat],\n          ],\n        },\n      }\n    }),\n  }\n}\n\n/**\n * Angular port of the UIPKGE VectorMap (React/Vue parity): a dark Mapbox globe\n * with pulsing pins, dashed flow routes, region selector chips, a globe/mercator\n * toggle, and telemetry cards for the active region and hovered pin.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-vector-map, [ui-vector-map]',\n  standalone: true,\n  imports: [UiMapComponent, UiMapSourceComponent, UiMapLayerComponent, UiMapMarkerComponent],\n  host: {\n    '[attr.data-slot]': '\"vector-map\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.aria-label]': 'ariaLabel || null',\n    '[class]': 'hostClass',\n    '[style.height]': 'heightStyle',\n  },\n  template: `\n    <ui-map\n      variant=\"dark\"\n      [projection]=\"currentProjection\"\n      [center]=\"mapCenter\"\n      [zoom]=\"1.6\"\n      [accessToken]=\"accessToken\"\n      class=\"size-full\"\n    >\n      @if (routesGeoJson) {\n        <ui-map-source id=\"vector-routes-source\" type=\"geojson\" [data]=\"routesGeoJson\">\n          <ui-map-layer id=\"vector-routes-layer\" type=\"line\" [paint]=\"routeLinePaint\" />\n        </ui-map-source>\n      }\n      @for (pin of pins; track $index) {\n        <ui-map-marker [lngLat]=\"[pin.lng ?? 0, pin.lat ?? 0]\" anchor=\"center\">\n          <div\n            class=\"relative flex size-6 cursor-pointer items-center justify-center select-none\"\n            (mouseenter)=\"hoveredPin = pin\"\n            (mouseleave)=\"hoveredPin = null\"\n            (click)=\"selectPin.emit(pin)\"\n          >\n            <span\n              class=\"absolute inline-flex size-full animate-ping rounded-full opacity-60\"\n              [style.background-color]=\"pin.color || pinFallback\"\n            ></span>\n            <span\n              class=\"ring-background relative inline-flex size-2.5 rounded-full shadow-xs ring-2\"\n              [style.background-color]=\"pin.color || pinFallback\"\n              [style.box-shadow]=\"'0 0 10px ' + (pin.color || pinFallback)\"\n            ></span>\n          </div>\n        </ui-map-marker>\n      }\n    </ui-map>\n\n    <div\n      class=\"border-border/70 bg-card/85 absolute top-3 left-3 z-10 hidden max-w-md flex-wrap gap-1 rounded-lg border p-1.5 shadow-xs backdrop-blur-md md:flex\"\n    >\n      @for (r of regions; track r.id) {\n        <button type=\"button\" [class]=\"chipClass(r.id)\" (click)=\"handleSelectRegion(r.id)\">\n          {{ r.name }}\n        </button>\n      }\n    </div>\n\n    <div\n      class=\"border-border/70 bg-card/85 absolute top-3 right-3 z-10 flex items-center gap-1 rounded-lg border p-1 shadow-xs backdrop-blur-md\"\n    >\n      <button\n        type=\"button\"\n        class=\"text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition focus-visible:ring-2 focus-visible:outline-none\"\n        (click)=\"toggleProjection()\"\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n          class=\"size-3.5\"\n          aria-hidden=\"true\"\n        >\n          <circle cx=\"12\" cy=\"12\" r=\"10\" />\n          <path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\" />\n          <path d=\"M2 12h20\" />\n        </svg>\n        <span class=\"capitalize\">{{ currentProjection }}</span>\n      </button>\n    </div>\n\n    @if (activeRecord; as record) {\n      <div\n        class=\"border-border/80 bg-card/95 animate-in fade-in slide-in-from-bottom-2 absolute bottom-3 left-3 z-10 max-w-sm rounded-xl border p-3.5 shadow-lg backdrop-blur-md duration-150\"\n      >\n        <div class=\"flex items-center gap-2\">\n          <span class=\"size-2 rounded-full\" [style.background-color]=\"record.color || pinFallback\"></span>\n          <h4 class=\"text-foreground text-sm font-semibold capitalize\">{{ displayRegionName }}</h4>\n          <span class=\"text-muted-foreground ml-auto font-mono text-xs uppercase\">{{ record.status || 'Optimal' }}</span>\n        </div>\n        @if (record.value !== undefined) {\n          <div class=\"border-border/60 mt-2.5 flex items-baseline justify-between border-t pt-2 font-mono text-xs\">\n            <span class=\"text-muted-foreground\">Active Nodes</span>\n            <span class=\"text-foreground font-semibold\">{{ record.value.toLocaleString() }}</span>\n          </div>\n        }\n        @if (record.description) {\n          <p class=\"text-muted-foreground mt-1.5 text-xs leading-relaxed\">{{ record.description }}</p>\n        }\n      </div>\n    }\n\n    @if (hoveredPin; as pin) {\n      <div\n        class=\"border-border/80 bg-card/95 animate-in fade-in slide-in-from-bottom-2 absolute right-3 bottom-3 z-10 max-w-xs rounded-xl border p-3 shadow-lg backdrop-blur-md duration-150\"\n      >\n        <div class=\"flex items-center gap-2\">\n          <span class=\"size-2 rounded-full\" [style.background-color]=\"pin.color || pinFallback\"></span>\n          <h5 class=\"text-foreground text-xs font-semibold\">{{ pin.label || 'Hub' }}</h5>\n        </div>\n        @if (pin.description) {\n          <p class=\"text-muted-foreground mt-1 text-xs\">{{ pin.description }}</p>\n        }\n        @if (pin.value !== undefined) {\n          <div class=\"border-border/60 mt-2 flex items-baseline justify-between border-t pt-1.5 font-mono text-xs\">\n            <span class=\"text-muted-foreground\">Throughput</span>\n            <span class=\"text-foreground font-semibold\">{{ pin.value }}</span>\n          </div>\n        }\n      </div>\n    }\n  `,\n})\nexport class UiVectorMapComponent implements OnChanges {\n  @Input() mode: 'countries' | 'continents' = 'continents'\n  @Input() regions: MapRegion[] = WORLD_REGIONS\n  @Input() regionData: Record<string, RegionDataRecord> = {}\n  /** Controlled active region id (pair with `selectedRegionChange`). */\n  @Input() selectedRegion?: string\n  @Input() pins: MapPin[] = []\n  @Input() routes: FlowRoute[] = []\n  @Input() height: number | string = 460\n  @Input() interactive = true\n  /** Reserved for React/Vue API parity (unused there too). */\n  @Input() showGraticule?: boolean\n  /** Reserved for React/Vue API parity (unused there too). */\n  @Input() showRegionLabels?: boolean\n  /** Reserved for React/Vue API parity (unused there too). */\n  @Input() fillColor?: string\n  /** Reserved for React/Vue API parity (unused there too). */\n  @Input() hoverColor?: string\n  /** Reserved for React/Vue API parity (unused there too). */\n  @Input() strokeColor?: string\n  @Input() projection: 'globe' | 'mercator' = 'globe'\n  /** Mapbox access token, passed through to the map (Angular-only: React/Vue read env). */\n  @Input() accessToken = ''\n  @Input() ariaLabel?: string\n  @Input('class') className?: string\n\n  /** React `onSelectedRegionChange`. */\n  @Output() selectedRegionChange = new EventEmitter<string>()\n  /** Vue `selectRegion`. */\n  @Output() selectRegion = new EventEmitter<string>()\n  /** Vue `selectPin`. */\n  @Output() selectPin = new EventEmitter<MapPin>()\n\n  currentProjection: 'globe' | 'mercator' = 'globe'\n  hoveredPin: MapPin | null = null\n  routesGeoJson: unknown | null = null\n  private internalRegion = 'northAmerica'\n\n  readonly mapCenter: [number, number] = [10, 25]\n  readonly pinFallback = PIN_FALLBACK\n  readonly routeLinePaint: Record<string, unknown> = {\n    'line-color': ['get', 'color'],\n    'line-width': 1.5,\n    'line-dasharray': [2, 2],\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (changes['projection']) this.currentProjection = this.projection\n    if (changes['routes']) this.routesGeoJson = buildRoutesGeoJson(this.routes)\n  }\n\n  get hostClass(): string {\n    return cn(\n      'block border-border bg-card group relative w-full overflow-hidden rounded-xl border shadow-xs',\n      this.className,\n    )\n  }\n\n  get heightStyle(): string {\n    if (typeof this.height === 'number') return `${this.height}px`\n    return /^\\d+$/.test(String(this.height)) ? `${this.height}px` : String(this.height)\n  }\n\n  get activeRegion(): string {\n    return this.selectedRegion ?? this.internalRegion\n  }\n\n  get activeRecord(): RegionDataRecord | null {\n    return this.regionData[this.activeRegion] || null\n  }\n\n  get displayRegionName(): string {\n    return this.activeRegion.replace(/([A-Z])/g, ' $1')\n  }\n\n  chipClass(id: string): string {\n    return cn(\n      'rounded-md px-2 py-0.5 text-xs font-medium transition focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none',\n      this.activeRegion === id\n        ? 'bg-primary text-primary-foreground font-semibold'\n        : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n    )\n  }\n\n  handleSelectRegion(id: string): void {\n    if (!this.interactive) return\n    this.internalRegion = id\n    this.selectedRegionChange.emit(id)\n    this.selectRegion.emit(id)\n  }\n\n  toggleProjection(): void {\n    this.currentProjection = this.currentProjection === 'globe' ? 'mercator' : 'globe'\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/vector-map/vector-map.component.ts"
    },
    {
      "path": "packages/registry-angular/components/charts/vector-map/index.ts",
      "content": "export {\n  UiVectorMapComponent,\n  WORLD_REGIONS,\n  WORLD_COUNTRIES,\n  projectPoint,\n  buildRoutesGeoJson,\n  type MapRegion,\n  type MapPin,\n  type FlowRoute,\n  type RegionDataRecord,\n} from './vector-map.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/vector-map/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/map.json"
  ],
  "description": "Interactive Mapbox globe/mercator vector map. Region selector chips, pulsing pins, dashed flow routes, and telemetry cards for the active region and hovered pin. Theme-aware via registry tokens.",
  "categories": [
    "chart"
  ]
}