{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "vector-map",
  "title": "Vector Map",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/vector-map/VectorMap.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface MapRegion {\n    id: string\n    name: string\n    path?: string\n  }\n\n  export 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\n  export 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\n  export interface RegionDataRecord {\n    value?: number\n    label?: string\n    status?: string\n    color?: string\n    description?: string\n  }\n\n  export interface VectorMapProps extends HTMLAttributes<HTMLDivElement> {\n    mode?: 'countries' | 'continents'\n    regions?: MapRegion[]\n    regionData?: Record<string, RegionDataRecord>\n    selectedRegion?: string\n    pins?: MapPin[]\n    routes?: FlowRoute[]\n    height?: number | string\n    interactive?: boolean\n    showGraticule?: boolean\n    showRegionLabels?: boolean\n    fillColor?: string\n    hoverColor?: string\n    strokeColor?: string\n    ariaLabel?: string\n    projection?: 'globe' | 'mercator'\n    /** React-parity selection callback (fires alongside the `bind:selectedRegion` update). */\n    onSelectedRegionChange?: (id: string) => void\n    /** @deprecated Renamed to `onSelectedRegionChange` for React parity; kept as an alias. */\n    onselectregion?: (id: string) => void\n    /** Fired when a pin is clicked. */\n    onSelectPin?: (pin: MapPin) => void\n    /** @deprecated Renamed to `onSelectPin` for React-style casing; kept as an alias. */\n    onselectpin?: (pin: MapPin) => void\n    ref?: HTMLDivElement | null\n  }\n\n  export 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\n  export 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\n  export function projectPoint(pt: { lat?: number; lng?: 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\n  const REGION_CENTROIDS: Record<string, [number, number]> = {\n    northAmerica: [-100, 45],\n    southAmerica: [-60, -15],\n    europe: [15, 52],\n    asia: [100, 40],\n    africa: [20, 5],\n    australiaOceania: [135, -25],\n    unitedKingdom: [-2, 54],\n    japan: [138, 38],\n  }\n  void REGION_CENTROIDS\n</script>\n\n<script lang=\"ts\">\n  import { untrack } from 'svelte'\n  import { Globe } from '@lucide/svelte'\n  import { Map, MapMarker, MapSource, MapLayer } from '$lib/components/ui/map'\n  import { cn } from '$lib/utils'\n\n  let {\n    mode: _mode = 'continents',\n    regions = WORLD_REGIONS,\n    regionData = {},\n    selectedRegion = $bindable(),\n    pins = [],\n    routes = [],\n    height = 460,\n    interactive = true,\n    projection = 'globe',\n    onSelectedRegionChange,\n    onselectregion,\n    onSelectPin,\n    onselectpin,\n    class: className,\n    ref = $bindable(null),\n    ...restProps\n  }: VectorMapProps = $props()\n\n  let activeRegion = $state(untrack(() => selectedRegion) || 'northAmerica')\n  let currentProjection = $state<'globe' | 'mercator'>(untrack(() => projection))\n  let hoveredPin = $state<MapPin | null>(null)\n\n  $effect(() => {\n    activeRegion = selectedRegion ?? 'northAmerica'\n  })\n\n  const activeRecord = $derived(regionData[activeRegion] || null)\n\n  function selectRegion(id: string) {\n    if (!interactive) return\n    activeRegion = id\n    selectedRegion = id\n    onSelectedRegionChange?.(id)\n    onselectregion?.(id)\n  }\n\n  function selectPin(pin: MapPin) {\n    onSelectPin?.(pin)\n    onselectpin?.(pin)\n  }\n\n  function toggleProjection() {\n    currentProjection = currentProjection === 'globe' ? 'mercator' : 'globe'\n  }\n\n  const routesGeoJson = $derived.by(() => {\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: {\n            color: r.color || 'rgba(56, 189, 248, 0.75)',\n          },\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  const routeLinePaint = {\n    'line-color': ['get', 'color'],\n    'line-width': 1.5,\n    'line-dasharray': [2, 2],\n  }\n\n  const heightStyle = $derived(\n    typeof height === 'number' ? `${height}px` : /^\\d+$/.test(String(height)) ? `${height}px` : height,\n  )\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"vector-map\"\n  class={cn('group relative w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs', className)}\n  style=\"height: {heightStyle}\"\n  {...restProps}\n>\n  <Map variant=\"dark\" projection={currentProjection} center={[10, 25]} zoom={1.6} class=\"size-full\">\n    <!-- Flow Routes Layer -->\n    {#if routesGeoJson}\n      <MapSource id=\"vector-routes-source\" type=\"geojson\" data={routesGeoJson}>\n        <MapLayer id=\"vector-routes-layer\" type=\"line\" paint={routeLinePaint} />\n      </MapSource>\n    {/if}\n\n    <!-- Pins Layer -->\n    {#each pins as pin, i (i)}\n      <MapMarker lngLat={[pin.lng ?? 0, pin.lat ?? 0]} anchor=\"center\" class=\"cursor-pointer select-none\">\n        <div\n          class=\"relative flex size-6 items-center justify-center\"\n          onmouseenter={() => (hoveredPin = pin)}\n          onmouseleave={() => (hoveredPin = null)}\n          onclick={() => selectPin(pin)}\n        >\n          <span\n            class=\"absolute inline-flex size-full animate-ping rounded-full opacity-60\"\n            style=\"background-color: {pin.color || 'oklch(0.65 0.20 145)'}\"\n          ></span>\n          <span\n            class=\"relative inline-flex size-2.5 rounded-full shadow-xs ring-2 ring-background\"\n            style=\"background-color: {pin.color ||\n              'oklch(0.65 0.20 145)'}; box-shadow: 0 0 10px {pin.color || 'oklch(0.65 0.20 145)'}\"\n          ></span>\n        </div>\n      </MapMarker>\n    {/each}\n  </Map>\n\n  <!-- Top-Left Region Selector Chips -->\n  <div\n    class=\"absolute top-3 left-3 z-10 hidden max-w-md flex-wrap gap-1 rounded-lg border border-border/70 bg-card/85 p-1.5 shadow-xs backdrop-blur-md md:flex\"\n  >\n    {#each regions as r (r.id)}\n      <button\n        type=\"button\"\n        class={cn(\n          'rounded-md px-2 py-0.5 text-xs font-medium transition',\n          activeRegion === r.id\n            ? 'bg-primary font-semibold text-primary-foreground'\n            : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n        )}\n        onclick={() => selectRegion(r.id)}\n      >\n        {r.name}\n      </button>\n    {/each}\n  </div>\n\n  <!-- Top-Right Controls -->\n  <div\n    class=\"absolute top-3 right-3 z-10 flex items-center gap-1 rounded-lg border border-border/70 bg-card/85 p-1 shadow-xs backdrop-blur-md\"\n  >\n    <button\n      type=\"button\"\n      class=\"flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground transition hover:bg-muted hover:text-foreground\"\n      onclick={toggleProjection}\n    >\n      <Globe class=\"size-3.5\" />\n      <span class=\"capitalize\">{currentProjection}</span>\n    </button>\n  </div>\n\n  <!-- Bottom-Left Region Detail Card -->\n  {#if activeRecord}\n    <div\n      class=\"absolute bottom-3 left-3 z-10 max-w-sm animate-in rounded-xl border border-border/80 bg-card/95 p-3.5 shadow-lg backdrop-blur-md duration-150 fade-in slide-in-from-bottom-2\"\n    >\n      <div class=\"flex items-center gap-2\">\n        <span class=\"size-2 rounded-full\" style=\"background-color: {activeRecord.color || 'oklch(0.65 0.20 145)'}\" ></span>\n        <h4 class=\"text-sm font-semibold text-foreground capitalize\">\n          {activeRegion.replace(/([A-Z])/g, ' $1')}\n        </h4>\n        <span class=\"ml-auto font-mono text-xs text-muted-foreground uppercase\">\n          {activeRecord.status || 'Optimal'}\n        </span>\n      </div>\n\n      {#if activeRecord.value !== undefined}\n        <div\n          class=\"mt-2.5 flex items-baseline justify-between border-t border-border/60 pt-2 font-mono text-xs\"\n        >\n          <span class=\"text-muted-foreground\">Active Nodes</span>\n          <span class=\"font-semibold text-foreground\">\n            {activeRecord.value.toLocaleString()}\n          </span>\n        </div>\n      {/if}\n\n      {#if activeRecord.description}\n        <p class=\"mt-1.5 text-xs leading-relaxed text-muted-foreground\">\n          {activeRecord.description}\n        </p>\n      {/if}\n    </div>\n  {/if}\n\n  <!-- Hovered Pin HUD -->\n  {#if hoveredPin}\n    <div\n      class=\"absolute right-3 bottom-3 z-10 max-w-xs animate-in rounded-xl border border-border/80 bg-card/95 p-3 shadow-lg backdrop-blur-md duration-150 fade-in slide-in-from-bottom-2\"\n    >\n      <div class=\"flex items-center gap-2\">\n        <span class=\"size-2 rounded-full\" style=\"background-color: {hoveredPin.color || 'oklch(0.65 0.20 145)'}\" ></span>\n        <h5 class=\"text-xs font-semibold text-foreground\">\n          {hoveredPin.label || 'Hub'}\n        </h5>\n      </div>\n      {#if hoveredPin.description}\n        <p class=\"mt-1 text-xs text-muted-foreground\">\n          {hoveredPin.description}\n        </p>\n      {/if}\n      {#if hoveredPin.value !== undefined}\n        <div class=\"mt-2 flex items-baseline justify-between border-t border-border/60 pt-1.5 font-mono text-xs\">\n          <span class=\"text-muted-foreground\">Throughput</span>\n          <span class=\"font-semibold text-foreground\">{hoveredPin.value}</span>\n        </div>\n      {/if}\n    </div>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/vector-map/VectorMap.svelte"
    },
    {
      "path": "packages/registry-svelte/components/vector-map/index.ts",
      "content": "export {\n  default as VectorMap,\n  default,\n  WORLD_REGIONS,\n  WORLD_COUNTRIES,\n  projectPoint,\n  type VectorMapProps,\n  type MapRegion,\n  type MapPin,\n  type FlowRoute,\n  type RegionDataRecord,\n} from './VectorMap.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/vector-map/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/map.json"
  ],
  "description": "Pure dependency-free SVG world landmass vector map. Features high-resolution continental paths (North America, South America, Europe, Africa, Asia, Oceania, UK, Japan) with region data shading, interactive pulsing pins, curved flight routes, and rich telemetry hover popovers.",
  "categories": [
    "chart"
  ]
}