{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "leaflet-map",
  "title": "Leaflet Map",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletMap.vue",
      "content": "<script setup lang=\"ts\">\n/**\n * LeafletMap — a thin, theme-aware Leaflet wrapper that renders free raster\n * tiles (OpenStreetMap, OpenTopoMap, Esri). No API key required.\n *\n * Drop `<LeafletMarker>` / `<LeafletPopup>` / `<LeafletPolyline>` /\n * `<LeafletPolygon>` / `<LeafletCircle>` / `<LeafletCircleMarker>` /\n * `<LeafletGeoJson>` / `<LeafletTileLayer>` into the default slot to build any\n * map — the same composition model as the Mapbox `map` component.\n *\n * Basemap `variant` presets (all key-free tile providers):\n * - `default`: theme-aware Esri light/dark canvas\n * - `streets` / `standard`: OpenStreetMap Standard\n * - `light` / `dark`: Esri Light/Dark Gray Canvas\n * - `muted`: theme-aware Esri canvas + desaturated tile pane\n * - `outdoors`: OpenTopoMap topographic contours\n * - `satellite`: Esri World Imagery\n * - `satellite-streets`: Esri imagery + Esri reference labels overlay\n * - `navigation-day`: Esri World Street Map\n * - `navigation-night`: Esri Dark Gray Canvas\n *\n * Not supported (Leaflet has no vector/GL renderer — these Mapbox props have\n * no counterpart): pitch, bearing, 3D buildings/terrain, globe projection.\n * Use a `variant` preset or `tile-url` for custom raster tiles instead.\n */\nimport { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { cn } from '@/lib/utils'\nimport {\n  defined,\n  fixDefaultLeafletIcon,\n  loadLeaflet,\n  toLatLng,\n  toLatLngBounds,\n  LEAFLET_MAP_KEY,\n  type LeafletPosition,\n} from './leaflet-context'\nimport {\n  leafletMapVariants,\n  LEAFLET_TILES,\n  LEAFLET_THEME_TILES,\n  type LeafletMapVariant,\n  type LeafletMapVariants,\n  type LeafletTilePreset,\n} from './leaflet-map.variants'\nimport 'leaflet/dist/leaflet.css'\nimport './leaflet-map.css'\n\nconst props = withDefaults(\n  defineProps<{\n    /** Named raster basemap preset ('streets' | 'outdoors' | 'satellite' | 'satellite-streets' | 'light' | 'dark' | 'navigation-day' | 'navigation-night' | 'standard' | 'muted' | 'default'). */\n    variant?: LeafletMapVariant\n    /** Height preset. Omit to size via `class` (blocks typically pass `size-full`). */\n    size?: LeafletMapVariants['size']\n    /** Custom raster tile URL template — overrides `variant`. */\n    tileUrl?: string\n    /** Attribution HTML for a custom `tile-url`. Defaults to the OpenStreetMap credit. */\n    tileAttribution?: string\n    /** Tile subdomains for a custom `tile-url` ('abcd' or ['a','b']). */\n    tileSubdomains?: string | string[]\n    /** Initial [lng, lat] — Mapbox order, matching the `map` component. */\n    center?: [number, number]\n    zoom?: number\n    minZoom?: number\n    /** Caps the map's max zoom. Defaults to the tile provider's own maxZoom. */\n    maxZoom?: number\n    /** Show the zoom control. */\n    navigation?: boolean\n    /** Placement of the zoom control ('top-left' | 'top-right' | 'bottom-left' | 'bottom-right'). */\n    navigationPosition?: LeafletPosition\n    /** Show the HTML5 fullscreen toggle button. */\n    fullscreen?: boolean\n    /** Placement of the fullscreen button. Defaults to 'top-right'. */\n    fullscreenPosition?: LeafletPosition\n    /** Show tile credits behind a ⓘ button (reveals on hover/tap). Keep on — OSM/Esri tiles require attribution. */\n    attribution?: boolean\n    /** Wheel zoom. Set false for maps embedded in scrollable pages. */\n    scrollWheelZoom?: boolean\n    /** Desaturate the tile pane to a quiet canvas (markers stay coloured). */\n    muted?: boolean\n    class?: string\n  }>(),\n  {\n    variant: 'default',\n    tileUrl: undefined,\n    tileAttribution: undefined,\n    tileSubdomains: undefined,\n    center: () => [0, 20],\n    zoom: 2,\n    minZoom: undefined,\n    maxZoom: undefined,\n    navigation: true,\n    navigationPosition: 'bottom-right',\n    fullscreen: false,\n    fullscreenPosition: 'top-right',\n    attribution: true,\n    scrollWheelZoom: true,\n    muted: false,\n  },\n)\n\nconst emit = defineEmits<{ (e: 'created', map: L.Map): void }>()\n\nconst htmlDark = ref(false)\nconst isMuted = computed(() => props.muted || props.variant === 'muted')\n\nconst resolvedTiles = computed<LeafletTilePreset>(() => {\n  if (props.tileUrl) {\n    return {\n      url: props.tileUrl,\n      attribution:\n        props.tileAttribution ??\n        '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors',\n      subdomains: props.tileSubdomains,\n      maxZoom: props.maxZoom,\n    }\n  }\n  if (\n    props.variant &&\n    props.variant !== 'default' &&\n    props.variant !== 'muted' &&\n    LEAFLET_TILES[props.variant as keyof typeof LEAFLET_TILES]\n  ) {\n    return LEAFLET_TILES[props.variant as keyof typeof LEAFLET_TILES]\n  }\n  return htmlDark.value ? LEAFLET_THEME_TILES.dark : LEAFLET_THEME_TILES.light\n})\n\n// Client-only: Leaflet needs the DOM, so SSR renders just the bg-muted shell.\nconst mounted = ref(false)\nconst inView = ref(false)\nconst containerRef = ref<HTMLDivElement | null>(null)\nconst mapEl = ref<HTMLDivElement | null>(null)\nconst map = shallowRef<L.Map | null>(null)\nlet Lmod: typeof import('leaflet') | null = null\nlet baseLayer: L.TileLayer | null = null\nlet overlayLayer: L.TileLayer | null = null\nlet resizeObserver: ResizeObserver | null = null\nlet intersectionObserver: IntersectionObserver | null = null\nlet themeObserver: MutationObserver | null = null\nlet onFullscreenChange: (() => void) | null = null\n\n// Published to LeafletMarker / LeafletPopup / … children.\nprovide(LEAFLET_MAP_KEY, map)\n\nfunction applyTiles(tiles: LeafletTilePreset) {\n  const m = map.value\n  const L = Lmod\n  if (!m || !L) return\n  baseLayer?.remove()\n  baseLayer = null\n  overlayLayer?.remove()\n  overlayLayer = null\n  baseLayer = L.tileLayer(tiles.url, {\n    attribution: tiles.attribution,\n    ...(tiles.subdomains ? { subdomains: tiles.subdomains } : {}),\n    maxZoom: tiles.maxZoom ?? 19,\n  })\n  baseLayer.addTo(m)\n  if (tiles.overlayUrl) {\n    overlayLayer = L.tileLayer(tiles.overlayUrl, { maxZoom: tiles.maxZoom ?? 19 })\n    overlayLayer.addTo(m)\n  }\n}\n\n// Collect attribution strings from every layer (base tiles, overlays, custom\n// LeafletTileLayers) — deduped, rendered by the ⓘ popover.\nconst attributions = ref<string[]>([])\nconst showAttribution = ref(false)\nfunction collectAttributions() {\n  const m = map.value\n  if (!m) return\n  const seen = new Set<string>()\n  m.eachLayer((layer) => {\n    const a = (layer as L.TileLayer).options?.attribution\n    if (typeof a === 'string' && a) seen.add(a)\n  })\n  attributions.value = [...seen]\n}\n\nfunction createMap() {\n  const el = mapEl.value\n  const L = Lmod\n  if (!el || !L || map.value) return\n  fixDefaultLeafletIcon(L)\n  const tiles = resolvedTiles.value\n  const m = L.map(\n    el,\n    defined({\n      center: toLatLng(props.center ?? [0, 20]),\n      zoom: props.zoom,\n      minZoom: props.minZoom,\n      maxZoom: props.maxZoom ?? tiles.maxZoom,\n      zoomControl: false,\n      attributionControl: false,\n      scrollWheelZoom: props.scrollWheelZoom,\n    }),\n  )\n  map.value = m\n  applyTiles(tiles)\n  m.on('zoomend', syncZoomBounds)\n  syncZoomBounds()\n  m.on('layeradd layerremove', collectAttributions)\n  collectAttributions()\n  emit('created', m)\n}\n\nonMounted(() => {\n  const root = document.documentElement\n  const syncTheme = () => {\n    htmlDark.value = root.classList.contains('dark')\n  }\n  syncTheme()\n  themeObserver = new MutationObserver(syncTheme)\n  themeObserver.observe(root, { attributes: true, attributeFilter: ['class'] })\n  mounted.value = true\n\n  const el = containerRef.value\n  if (!el || typeof IntersectionObserver === 'undefined') {\n    inView.value = true\n  } else {\n    intersectionObserver = new IntersectionObserver(\n      ([entry]) => {\n        inView.value = entry.isIntersecting\n      },\n      { rootMargin: '160px', threshold: 0.01 },\n    )\n    intersectionObserver.observe(el)\n  }\n  if (typeof ResizeObserver !== 'undefined' && el) {\n    resizeObserver = new ResizeObserver(() => {\n      map.value?.invalidateSize()\n    })\n    resizeObserver.observe(el)\n  }\n\n  onFullscreenChange = () => {\n    isFullscreen.value = Boolean(document.fullscreenElement)\n  }\n  document.addEventListener('fullscreenchange', onFullscreenChange)\n})\n\nonUnmounted(() => {\n  resizeObserver?.disconnect()\n  intersectionObserver?.disconnect()\n  themeObserver?.disconnect()\n  if (onFullscreenChange) document.removeEventListener('fullscreenchange', onFullscreenChange)\n  map.value?.remove()\n  map.value = null\n})\n\n// Leaflet module loads lazily once the container is both mounted and in view.\nwatch([mounted, inView], async ([isMounted, visible]) => {\n  if (!isMounted || !visible || map.value) return\n  Lmod = await loadLeaflet()\n  createMap()\n})\n\n// Retile on variant / theme / custom-tile changes.\nwatch(resolvedTiles, (tiles) => {\n  if (map.value) applyTiles(tiles)\n})\n\nwatch(\n  () => [props.center, props.zoom],\n  () => {\n    const m = map.value\n    if (!m || !props.center) return\n    m.setView(toLatLng(props.center), props.zoom)\n  },\n)\n\nwatch(\n  () => props.attribution,\n  () => {\n    if (!props.attribution) showAttribution.value = false\n  },\n)\n\nwatch(\n  () => props.scrollWheelZoom,\n  (enabled) => {\n    if (!map.value) return\n    if (enabled) map.value.scrollWheelZoom.enable()\n    else map.value.scrollWheelZoom.disable()\n  },\n)\n\nconst isFullscreen = ref(false)\nfunction toggleFullscreen() {\n  const el = containerRef.value\n  if (!el) return\n  if (document.fullscreenElement) document.exitFullscreen()\n  else el.requestFullscreen?.()\n}\n\n// Zoom/fullscreen chrome is plain HTML overlaid on the map (like Mapbox's\n// controls) — a corner stack per occupied corner, zoom group above fullscreen.\nconst cornerOrder: LeafletPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\nconst cornerClasses = computed<Record<LeafletPosition, string>>(() => ({\n  'top-left': 'left-3 top-3',\n  'top-right': 'right-3 top-3',\n  // above the ⓘ button (bottom-left) when credits are shown\n  'bottom-left': props.attribution && attributions.value.length ? 'bottom-9 left-3' : 'bottom-3 left-3',\n  'bottom-right': 'bottom-3 right-3',\n}))\nconst navPosition = computed(() => props.navigationPosition ?? 'bottom-right')\nconst fsPosition = computed(() => props.fullscreenPosition ?? 'top-right')\n\nconst canZoomIn = ref(true)\nconst canZoomOut = ref(true)\nfunction syncZoomBounds() {\n  const m = map.value\n  if (!m) return\n  canZoomIn.value = m.getZoom() < m.getMaxZoom()\n  canZoomOut.value = m.getZoom() > m.getMinZoom()\n}\n\n/** Mapbox-style camera shim — accepts { center: [lng, lat], zoom, duration(ms) }. */\nfunction flyTo(options: { center?: [number, number]; zoom?: number; duration?: number } = {}) {\n  const m = map.value\n  if (!m) return\n  const target = options.center ? toLatLng(options.center) : m.getCenter()\n  m.flyTo(target, options.zoom ?? m.getZoom(), {\n    duration: (options.duration ?? 800) / 1000,\n  })\n}\n\nfunction setView(options: { center?: [number, number]; zoom?: number } = {}) {\n  const m = map.value\n  if (!m) return\n  m.setView(options.center ? toLatLng(options.center) : m.getCenter(), options.zoom ?? m.getZoom())\n}\n\nfunction jumpTo(options: { center?: [number, number]; zoom?: number } = {}) {\n  const m = map.value\n  if (!m) return\n  m.setView(options.center ? toLatLng(options.center) : m.getCenter(), options.zoom ?? m.getZoom(), {\n    animate: false,\n  })\n}\n\n// <script setup> components are closed by default, so a template ref on\n// <LeafletMap> hands back this object. It mirrors the `map` component's MapRef\n// surface: camera helpers plus `map` / `getMap()` for the raw L.Map.\ndefineExpose({\n  /** The underlying Leaflet Map, or null until it is created. */\n  get map() {\n    return map.value\n  },\n  getMap: () => map.value,\n  flyTo,\n  setView,\n  jumpTo,\n  /** [[west,south],[east,north]] in [lng, lat], or any Leaflet bounds expression. */\n  fitBounds: (bounds: [[number, number], [number, number]] | L.LatLngBoundsExpression, options?: L.FitBoundsOptions) =>\n    map.value?.fitBounds(toLatLngBounds(bounds as [[number, number], [number, number]]), options),\n  panTo: (center: [number, number]) => map.value?.panTo(toLatLng(center)),\n  zoomIn: () => map.value?.zoomIn(),\n  zoomOut: () => map.value?.zoomOut(),\n  resize: () => map.value?.invalidateSize(),\n})\n</script>\n\n<template>\n  <div\n    ref=\"containerRef\"\n    data-uipkge\n    data-slot=\"leaflet-map\"\n    :data-variant=\"variant\"\n    :data-muted=\"isMuted\"\n    :class=\"cn(leafletMapVariants({ variant, ...(size ? { size } : {}) }), props.class)\"\n  >\n    <div v-show=\"inView\" ref=\"mapEl\" class=\"size-full\" />\n    <template v-for=\"corner in cornerOrder\" :key=\"corner\">\n      <div\n        v-if=\"map && ((navigation && navPosition === corner) || (fullscreen && fsPosition === corner))\"\n        class=\"absolute z-[1000] flex flex-col gap-2.5\"\n        :class=\"cornerClasses[corner]\"\n      >\n        <div\n          v-if=\"navigation && navPosition === corner\"\n          class=\"border-border bg-card divide-border flex flex-col divide-y overflow-hidden rounded-lg border shadow-sm\"\n        >\n          <button\n            type=\"button\"\n            class=\"text-muted-foreground hover:bg-muted flex size-8 items-center justify-center transition-colors disabled:pointer-events-none disabled:opacity-40\"\n            aria-label=\"Zoom in\"\n            :disabled=\"!canZoomIn\"\n            @click=\"map?.zoomIn()\"\n          >\n            <svg class=\"size-full\" viewBox=\"0 0 29 29\" fill=\"currentColor\" aria-hidden=\"true\">\n              <path\n                d=\"M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z\"\n              />\n            </svg>\n          </button>\n          <button\n            type=\"button\"\n            class=\"text-muted-foreground hover:bg-muted flex size-8 items-center justify-center transition-colors disabled:pointer-events-none disabled:opacity-40\"\n            aria-label=\"Zoom out\"\n            :disabled=\"!canZoomOut\"\n            @click=\"map?.zoomOut()\"\n          >\n            <svg class=\"size-full\" viewBox=\"0 0 29 29\" fill=\"currentColor\" aria-hidden=\"true\">\n              <path d=\"M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z\" />\n            </svg>\n          </button>\n        </div>\n        <button\n          v-if=\"fullscreen && fsPosition === corner\"\n          type=\"button\"\n          class=\"border-border bg-card text-muted-foreground hover:bg-muted flex size-8 items-center justify-center rounded-lg border shadow-sm transition-colors\"\n          aria-label=\"Toggle fullscreen\"\n          @click=\"toggleFullscreen\"\n        >\n          <svg class=\"size-full\" viewBox=\"0 0 29 29\" fill=\"currentColor\" aria-hidden=\"true\">\n            <path\n              v-if=\"isFullscreen\"\n              d=\"M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z\"\n            />\n            <path\n              v-else\n              d=\"M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z\"\n            />\n          </svg>\n        </button>\n      </div>\n    </template>\n    <!-- Tile credits behind a Mapbox-style ⓘ button: hover reveals on desktop,\n         tap toggles on touch. Keep visible — OSM/Esri tiles require credit. -->\n    <div\n      v-if=\"map && attribution && attributions.length\"\n      class=\"group absolute bottom-3 left-3 z-[1000] flex flex-col items-start gap-1.5\"\n    >\n      <div\n        class=\"border-border bg-popover text-popover-foreground max-w-64 rounded-md border px-2.5 py-1.5 text-[11px] leading-relaxed shadow-md transition-opacity [&_a]:underline\"\n        :class=\"\n          showAttribution ? 'visible opacity-100' : 'invisible opacity-0 group-hover:visible group-hover:opacity-100'\n        \"\n        role=\"note\"\n        v-html=\"attributions.join(' | ')\"\n      />\n      <button\n        type=\"button\"\n        class=\"border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground flex size-4 items-center justify-center rounded-full border shadow-xs transition-colors\"\n        aria-label=\"Map data attribution\"\n        :aria-expanded=\"showAttribution\"\n        @click=\"showAttribution = !showAttribution\"\n      >\n        <svg\n          class=\"size-3\"\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          aria-hidden=\"true\"\n        >\n          <circle cx=\"12\" cy=\"12\" r=\"10\" />\n          <path d=\"M12 16v-4M12 8h.01\" />\n        </svg>\n      </button>\n    </div>\n    <slot />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletMap.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletMarker.vue",
      "content": "<script setup lang=\"ts\">\nimport { Comment, Text, computed, ref, useSlots, watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, toLatLng, useLeafletLayer } from './leaflet-context'\n\ntype MarkerAnchor =\n  | 'center'\n  | 'top'\n  | 'bottom'\n  | 'left'\n  | 'right'\n  | 'top-left'\n  | 'top-right'\n  | 'bottom-left'\n  | 'bottom-right'\n\nconst props = withDefaults(\n  defineProps<{\n    /** [lng, lat] — Mapbox order, matching the `map` component's MapMarker. */\n    lngLat: [number, number]\n    /** Which edge/corner of the marker content sits on the coordinate. */\n    anchor?: MarkerAnchor\n    draggable?: boolean\n    opacity?: number\n    zIndexOffset?: number\n    title?: string\n    alt?: string\n  }>(),\n  { anchor: 'center' },\n)\n\nconst emit = defineEmits<{\n  (e: 'click', ev: L.LeafletMouseEvent): void\n  (e: 'ready', marker: L.Marker): void\n}>()\n\nconst slots = useSlots()\nconst el = ref<HTMLElement | null>(null)\n\n// LeafletPopup / LeafletTooltip children bind to the marker; every other slot\n// node becomes custom div-icon content (a real element — listeners survive).\nconst hasIconContent = computed(() =>\n  (slots.default?.() ?? []).some((n) => {\n    if (n.type === Comment || n.type === Text) return false\n    const name = (n.type as any)?.__name ?? (n.type as any)?.name\n    return name !== 'LeafletPopup' && name !== 'LeafletTooltip'\n  }),\n)\n\nconst layer = useLeafletLayer((map, L) => {\n  const options: L.MarkerOptions = defined({\n    interactive: true,\n    draggable: props.draggable,\n    opacity: props.opacity,\n    zIndexOffset: props.zIndexOffset,\n    title: props.title,\n    alt: props.alt,\n  })\n  if (hasIconContent.value && el.value) {\n    options.icon = L.divIcon({ className: 'uipkge-leaflet-div-icon', html: el.value })\n  }\n  const marker = L.marker(toLatLng(props.lngLat), options)\n  marker.on('click', (ev) => emit('click', ev))\n  return marker\n})\n\nwatch(\n  layer,\n  (m) => {\n    if (m) emit('ready', m)\n  },\n  { immediate: true },\n)\n\nwatch(\n  () => props.lngLat,\n  (v) => {\n    if (v) layer.value?.setLatLng(toLatLng(v))\n  },\n)\nwatch(\n  () => props.opacity,\n  (v) => {\n    if (v !== undefined) layer.value?.setOpacity(v)\n  },\n)\nwatch(\n  () => props.zIndexOffset,\n  (v) => {\n    if (v !== undefined) layer.value?.setZIndexOffset(v)\n  },\n)\n</script>\n\n<template>\n  <div ref=\"el\" class=\"uipkge-leaflet-anchor\" :data-anchor=\"anchor\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletMarker.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletPopup.vue",
      "content": "<script setup lang=\"ts\">\nimport { onUnmounted, ref, watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, loadLeaflet, toLatLng, useLeafletMap, useParentLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletPopup' })\n\nconst props = withDefaults(\n  defineProps<{\n    /** [lng, lat] — standalone popup on the map. Omit inside a layer to bind to it. */\n    lngLat?: [number, number]\n    maxWidth?: number\n    /** Minimum popup width. Defaults to 200 — keeps card-style content from collapsing narrow. */\n    minWidth?: number\n    offset?: [number, number]\n    className?: string\n    autoClose?: boolean\n    closeOnClick?: boolean\n    closeButton?: boolean\n    keepInView?: boolean\n  }>(),\n  { minWidth: 200 },\n)\n\nconst map = useLeafletMap()\nconst parentLayer = useParentLeafletLayer()\nconst el = ref<HTMLElement | null>(null)\nlet popup: L.Popup | null = null\nlet boundTo: L.Layer | null = null\n\nwatch(\n  [map, () => parentLayer?.value],\n  async ([m, layer]) => {\n    if (!m || !el.value) return\n    const L = await loadLeaflet()\n    if (layer) {\n      boundTo = layer\n      layer.bindPopup(el.value, defined({ ...props, offset: props.offset }))\n      return\n    }\n    if (props.lngLat && !popup) {\n      popup = L.popup(defined({ ...props, offset: props.offset }))\n        .setLatLng(toLatLng(props.lngLat))\n        .setContent(el.value)\n      popup.openOn(m)\n    }\n  },\n  { immediate: true },\n)\n\nonUnmounted(() => {\n  try {\n    boundTo?.unbindPopup()\n    popup?.remove()\n  } catch {\n    /* map already destroyed */\n  }\n})\n</script>\n\n<template>\n  <div ref=\"el\" class=\"uipkge-leaflet-popup-src\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletPopup.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletTooltip.vue",
      "content": "<script setup lang=\"ts\">\nimport { onUnmounted, ref, watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, loadLeaflet, toLatLng, useLeafletMap, useParentLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletTooltip' })\n\nconst props = withDefaults(\n  defineProps<{\n    /** [lng, lat] — standalone tooltip on the map. Omit inside a layer to bind to it. */\n    lngLat?: [number, number]\n    offset?: [number, number]\n    direction?: 'top' | 'bottom' | 'left' | 'right' | 'center' | 'auto'\n    permanent?: boolean\n    sticky?: boolean\n    opacity?: number\n    className?: string\n    interactive?: boolean\n  }>(),\n  {},\n)\n\nconst map = useLeafletMap()\nconst parentLayer = useParentLeafletLayer()\nconst el = ref<HTMLElement | null>(null)\nlet tooltip: L.Tooltip | null = null\nlet boundTo: L.Layer | null = null\n\nwatch(\n  [map, () => parentLayer?.value],\n  async ([m, layer]) => {\n    if (!m || !el.value) return\n    const L = await loadLeaflet()\n    if (layer) {\n      boundTo = layer\n      layer.bindTooltip(el.value, defined({ ...props, offset: props.offset }))\n      return\n    }\n    if (props.lngLat && !tooltip) {\n      tooltip = L.tooltip(defined({ ...props, offset: props.offset }))\n        .setLatLng(toLatLng(props.lngLat))\n        .setContent(el.value)\n      tooltip.addTo(m)\n    }\n  },\n  { immediate: true },\n)\n\nonUnmounted(() => {\n  try {\n    boundTo?.unbindTooltip()\n    tooltip?.remove()\n  } catch {\n    /* map already destroyed */\n  }\n})\n</script>\n\n<template>\n  <div ref=\"el\" class=\"uipkge-leaflet-tooltip-src\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletTooltip.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletPolyline.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, toLatLngs, useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletPolyline' })\n\nconst props = defineProps<{\n  /** Path points as [lng, lat][] — or [lng, lat][][] for multi-part lines. */\n  lngLatPath: [number, number][] | [number, number][][]\n  color?: string\n  weight?: number\n  opacity?: number\n  lineCap?: 'butt' | 'round' | 'square'\n  lineJoin?: 'miter' | 'round' | 'bevel'\n  dashArray?: string | number[]\n  dashOffset?: string\n  smoothFactor?: number\n  noClip?: boolean\n  className?: string\n}>()\n\nconst emit = defineEmits<{ (e: 'click', ev: L.LeafletMouseEvent): void }>()\n\nconst pathOptions = () =>\n  defined({\n    color: props.color,\n    weight: props.weight,\n    opacity: props.opacity,\n    lineCap: props.lineCap,\n    lineJoin: props.lineJoin,\n    dashArray: props.dashArray,\n    dashOffset: props.dashOffset,\n    smoothFactor: props.smoothFactor,\n    noClip: props.noClip,\n    className: props.className,\n    interactive: true,\n  } as L.PolylineOptions)\n\nconst layer = useLeafletLayer((map, L) => {\n  const line = L.polyline(toLatLngs(props.lngLatPath) as L.LatLngExpression[], pathOptions())\n  line.on('click', (ev) => emit('click', ev))\n  return line\n})\n\nwatch(\n  () => props.lngLatPath,\n  (v) => layer.value?.setLatLngs(toLatLngs(v) as L.LatLngExpression[]),\n  { deep: true },\n)\n\nwatch(\n  () => [props.color, props.weight, props.opacity, props.dashArray, props.dashOffset, props.lineCap, props.lineJoin],\n  () => layer.value?.setStyle(pathOptions()),\n)\n</script>\n\n<template>\n  <div class=\"hidden\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletPolyline.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletPolygon.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, toLatLngs, useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletPolygon' })\n\nconst props = defineProps<{\n  /** Ring points as [lng, lat][] — or [lng, lat][][] for holes/multi-polygons. */\n  lngLatPath: [number, number][] | [number, number][][]\n  color?: string\n  weight?: number\n  opacity?: number\n  lineCap?: 'butt' | 'round' | 'square'\n  lineJoin?: 'miter' | 'round' | 'bevel'\n  dashArray?: string | number[]\n  dashOffset?: string\n  fill?: boolean\n  fillColor?: string\n  fillOpacity?: number\n  className?: string\n}>()\n\nconst emit = defineEmits<{ (e: 'click', ev: L.LeafletMouseEvent): void }>()\n\nconst pathOptions = () =>\n  defined({\n    color: props.color,\n    weight: props.weight,\n    opacity: props.opacity,\n    lineCap: props.lineCap,\n    lineJoin: props.lineJoin,\n    dashArray: props.dashArray,\n    dashOffset: props.dashOffset,\n    fill: props.fill,\n    fillColor: props.fillColor,\n    fillOpacity: props.fillOpacity,\n    className: props.className,\n    interactive: true,\n  } as L.PolylineOptions)\n\nconst layer = useLeafletLayer((map, L) => {\n  const polygon = L.polygon(toLatLngs(props.lngLatPath) as L.LatLngExpression[], pathOptions())\n  polygon.on('click', (ev) => emit('click', ev))\n  return polygon\n})\n\nwatch(\n  () => props.lngLatPath,\n  (v) => layer.value?.setLatLngs(toLatLngs(v) as L.LatLngExpression[]),\n  { deep: true },\n)\n\nwatch(\n  () => [props.color, props.weight, props.opacity, props.fill, props.fillColor, props.fillOpacity, props.dashArray],\n  () => layer.value?.setStyle(pathOptions()),\n)\n</script>\n\n<template>\n  <div class=\"hidden\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletPolygon.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletCircle.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, toLatLng, useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletCircle' })\n\nconst props = defineProps<{\n  /** [lng, lat] — Mapbox order. */\n  center: [number, number]\n  /** Radius in meters. */\n  radius?: number\n  color?: string\n  weight?: number\n  opacity?: number\n  dashArray?: string | number[]\n  fill?: boolean\n  fillColor?: string\n  fillOpacity?: number\n  className?: string\n}>()\n\nconst emit = defineEmits<{ (e: 'click', ev: L.LeafletMouseEvent): void }>()\n\nconst pathOptions = () =>\n  defined({\n    color: props.color,\n    weight: props.weight,\n    opacity: props.opacity,\n    dashArray: props.dashArray,\n    fill: props.fill,\n    fillColor: props.fillColor,\n    fillOpacity: props.fillOpacity,\n    className: props.className,\n    interactive: true,\n  } as L.CircleMarkerOptions)\n\nconst layer = useLeafletLayer((map, L) => {\n  const circle = L.circle(toLatLng(props.center), { ...pathOptions(), radius: props.radius })\n  circle.on('click', (ev) => emit('click', ev))\n  return circle\n})\n\nwatch(\n  () => props.center,\n  (v) => {\n    if (v) layer.value?.setLatLng(toLatLng(v))\n  },\n)\nwatch(\n  () => props.radius,\n  (v) => {\n    if (v !== undefined) layer.value?.setRadius(v)\n  },\n)\nwatch(\n  () => [props.color, props.weight, props.opacity, props.fill, props.fillColor, props.fillOpacity, props.dashArray],\n  () => layer.value?.setStyle(pathOptions()),\n)\n</script>\n\n<template>\n  <div class=\"hidden\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletCircle.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletCircleMarker.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { defined, toLatLng, useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletCircleMarker' })\n\nconst props = defineProps<{\n  /** [lng, lat] — Mapbox order. */\n  center: [number, number]\n  /** Radius in pixels. */\n  radius?: number\n  color?: string\n  weight?: number\n  opacity?: number\n  dashArray?: string | number[]\n  fill?: boolean\n  fillColor?: string\n  fillOpacity?: number\n  className?: string\n}>()\n\nconst emit = defineEmits<{ (e: 'click', ev: L.LeafletMouseEvent): void }>()\n\nconst pathOptions = () =>\n  defined({\n    color: props.color,\n    weight: props.weight,\n    opacity: props.opacity,\n    dashArray: props.dashArray,\n    fill: props.fill,\n    fillColor: props.fillColor,\n    fillOpacity: props.fillOpacity,\n    className: props.className,\n    interactive: true,\n  } as L.CircleMarkerOptions)\n\nconst layer = useLeafletLayer((map, L) => {\n  const marker = L.circleMarker(toLatLng(props.center), { ...pathOptions(), radius: props.radius })\n  marker.on('click', (ev) => emit('click', ev))\n  return marker\n})\n\nwatch(\n  () => props.center,\n  (v) => {\n    if (v) layer.value?.setLatLng(toLatLng(v))\n  },\n)\nwatch(\n  () => props.radius,\n  (v) => {\n    if (v !== undefined) layer.value?.setRadius(v)\n  },\n)\nwatch(\n  () => [props.color, props.weight, props.opacity, props.fill, props.fillColor, props.fillOpacity, props.dashArray],\n  () => layer.value?.setStyle(pathOptions()),\n)\n</script>\n\n<template>\n  <div class=\"hidden\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletCircleMarker.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletGeoJson.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport type * as L from 'leaflet'\nimport { useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletGeoJson' })\n\nconst props = defineProps<{\n  /** GeoJSON FeatureCollection / Feature / geometry. */\n  geojson: GeoJSON.GeoJSON\n  /** Leaflet GeoJSON options: `style`, `pointToLayer`, `onEachFeature`, `filter`, `coordsToLatLng`. */\n  options?: L.GeoJSONOptions\n}>()\n\nconst emit = defineEmits<{ (e: 'click', ev: L.LeafletMouseEvent): void }>()\n\nconst layer = useLeafletLayer((map, L) => {\n  const gj = L.geoJSON(props.geojson as any, props.options)\n  gj.on('click', (ev) => emit('click', ev))\n  return gj\n})\n\nwatch(\n  () => props.geojson,\n  (v) => {\n    if (!layer.value || !v) return\n    layer.value.clearLayers()\n    layer.value.addData(v as any)\n  },\n  { deep: true },\n)\n</script>\n\n<template>\n  <div class=\"hidden\"><slot /></div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletGeoJson.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/LeafletTileLayer.vue",
      "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue'\nimport { defined, useLeafletLayer } from './leaflet-context'\n\ndefineOptions({ name: 'LeafletTileLayer' })\n\nconst props = defineProps<{\n  /** Raster tile URL template ({z}/{x}/{y}, optional {s} subdomains + {r} retina). */\n  url: string\n  attribution?: string\n  subdomains?: string | string[]\n  minZoom?: number\n  maxZoom?: number\n  opacity?: number\n  zIndex?: number\n  tms?: boolean\n}>()\n\nconst layer = useLeafletLayer((map, L) =>\n  L.tileLayer(\n    props.url,\n    defined({\n      attribution: props.attribution,\n      subdomains: props.subdomains,\n      minZoom: props.minZoom,\n      maxZoom: props.maxZoom,\n      opacity: props.opacity,\n      zIndex: props.zIndex,\n      tms: props.tms,\n    }),\n  ),\n)\n\nwatch(\n  () => props.url,\n  (url) => {\n    if (url) layer.value?.setUrl(url)\n  },\n)\nwatch(\n  () => props.opacity,\n  (v) => {\n    if (v !== undefined) layer.value?.setOpacity(v)\n  },\n)\nwatch(\n  () => props.zIndex,\n  (v) => {\n    if (v !== undefined) layer.value?.setZIndex(v)\n  },\n)\n</script>\n\n<template>\n  <div class=\"hidden\" />\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/LeafletTileLayer.vue"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/leaflet-context.ts",
      "content": "import { inject, onUnmounted, provide, shallowRef, watch, type ShallowRef } from 'vue'\nimport type * as L from 'leaflet'\n\nexport const LEAFLET_MAP_KEY = 'uipkge-leaflet-map'\nexport const LEAFLET_LAYER_KEY = 'uipkge-leaflet-layer'\n\ntype LeafletModule = typeof import('leaflet')\n\nlet leafletPromise: Promise<LeafletModule> | null = null\n\n/**\n * Lazily loads Leaflet's ESM build. Leaflet touches `window`/`document` at\n * import time, so a static top-level import would crash SSR renders — every\n * consumer of this helper resolves the module only on the client.\n */\nexport function loadLeaflet(): Promise<LeafletModule> {\n  if (!leafletPromise) leafletPromise = import('leaflet')\n  return leafletPromise\n}\n\n/** [lng, lat] (Mapbox order, matching the `map` component) -> Leaflet [lat, lng]. */\nexport function toLatLng(c: [number, number]): L.LatLngExpression {\n  return [c[1], c[0]]\n}\n\n/** Converts a [lng, lat][] path to Leaflet [lat, lng][]. */\nexport function toLatLngs(\n  path: [number, number][] | [number, number][][],\n): L.LatLngExpression[] | L.LatLngExpression[][] {\n  if (!path.length) return []\n  return Array.isArray(path[0][0])\n    ? (path as [number, number][][]).map((ring) => ring.map(toLatLng))\n    : (path as [number, number][]).map(toLatLng)\n}\n\n/** [[west,south],[east,north]] in [lng, lat] -> a bounds literal Leaflet accepts. */\nexport function toLatLngBounds(bounds: [[number, number], [number, number]]): L.LatLngBoundsExpression {\n  return [toLatLng(bounds[0]), toLatLng(bounds[1])] as L.LatLngBoundsExpression\n}\n\nexport type LeafletPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/**\n * Leaflet merges layer options by assignment, so passing `undefined` would\n * clobber its defaults (e.g. `subdomains: 'abc'` -> crash). Strip undefined\n * keys before handing options to Leaflet.\n */\nexport function defined<T extends object>(o: T): T {\n  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as T\n}\n\n/** Injects the map instance ref published by the enclosing `<LeafletMap>`. */\nexport function useLeafletMap(): ShallowRef<L.Map | null> {\n  const map = inject<ShallowRef<L.Map | null>>(LEAFLET_MAP_KEY)\n  if (!map) {\n    throw new Error('uipkge: Leaflet* components must be rendered inside <LeafletMap>.')\n  }\n  return map\n}\n\n/**\n * Waits for the enclosing `<LeafletMap>` instance, builds the layer once, adds\n * it to the map, and removes it on unmount. The layer is also provided so\n * nested `<LeafletPopup>` / `<LeafletTooltip>` children can bind to it.\n */\nexport function useLeafletLayer<T extends L.Layer>(\n  build: (map: L.Map, L: LeafletModule) => T,\n  opts: { addToMap?: boolean } = {},\n): ShallowRef<T | null> {\n  const mapRef = useLeafletMap()\n  const layer = shallowRef<T | null>(null) as ShallowRef<T | null>\n  provide(LEAFLET_LAYER_KEY, layer as ShallowRef<L.Layer | null>)\n\n  const stop = watch(\n    mapRef,\n    async (map) => {\n      if (!map || layer.value) return\n      const L = await loadLeaflet()\n      if (layer.value) return\n      const instance = build(map, L)\n      layer.value = instance\n      if (opts.addToMap !== false) instance.addTo(map)\n    },\n    { immediate: true },\n  )\n\n  onUnmounted(() => {\n    stop()\n    layer.value?.remove()\n    layer.value = null\n  })\n\n  return layer\n}\n\n/** The nearest ancestor layer (marker, polyline, …) a popup/tooltip binds to. */\nexport function useParentLeafletLayer(): ShallowRef<L.Layer | null> | null {\n  return inject<ShallowRef<L.Layer | null> | null>(LEAFLET_LAYER_KEY, null)\n}\n\nconst LEAFLET_ICON_BASE = 'https://unpkg.com/leaflet@1.9.4/dist/images'\nlet defaultIconFixed = false\n\n/**\n * Leaflet's default pin references image paths relative to the CSS file, which\n * bundlers can't resolve — point them at the versioned unpkg assets instead.\n * Only matters for markers without custom slot content.\n */\nexport function fixDefaultLeafletIcon(L: LeafletModule) {\n  if (defaultIconFixed) return\n  defaultIconFixed = true\n  L.Icon.Default.mergeOptions({\n    iconRetinaUrl: `${LEAFLET_ICON_BASE}/marker-icon-2x.png`,\n    iconUrl: `${LEAFLET_ICON_BASE}/marker-icon.png`,\n    shadowUrl: `${LEAFLET_ICON_BASE}/marker-shadow.png`,\n  })\n}\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/leaflet-context.ts"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/leaflet-map.variants.ts",
      "content": "import { cva, type VariantProps } from 'class-variance-authority'\n\nexport type LeafletMapVariant =\n  | 'default'\n  | 'muted'\n  | 'streets'\n  | 'outdoors'\n  | 'light'\n  | 'dark'\n  | 'satellite'\n  | 'satellite-streets'\n  | 'navigation-day'\n  | 'navigation-night'\n  | 'standard'\n\nexport interface LeafletTilePreset {\n  /** Raster tile URL template ({z}/{x}/{y}, optional {s} subdomains + {r} retina). */\n  url: string\n  /** Required provider attribution HTML. */\n  attribution: string\n  subdomains?: string | string[]\n  maxZoom?: number\n  /** Optional label/boundary overlay composited above the base tiles. */\n  overlayUrl?: string\n}\n\nconst OSM_ATTR = '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'\nconst TOPO_ATTR = `${OSM_ATTR} | map style: &copy; <a href=\"https://opentopomap.org\">OpenTopoMap</a> (<a href=\"https://creativecommons.org/licenses/by-sa/3.0/\">CC-BY-SA</a>)`\nconst ESRI_ATTR = 'Tiles &copy; Esri &mdash; Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community'\n\nconst OSM_STANDARD: LeafletTilePreset = {\n  url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',\n  attribution: OSM_ATTR,\n  maxZoom: 19,\n}\n// CARTO basemaps moved behind an API key — Esri Canvas/Street services stay key-free.\nconst ESRI_LIGHT: LeafletTilePreset = {\n  url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',\n  attribution: ESRI_ATTR,\n  maxZoom: 16,\n  overlayUrl:\n    'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',\n}\nconst ESRI_DARK: LeafletTilePreset = {\n  url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',\n  attribution: ESRI_ATTR,\n  maxZoom: 16,\n  overlayUrl:\n    'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',\n}\nconst ESRI_STREETS: LeafletTilePreset = {\n  url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',\n  attribution: ESRI_ATTR,\n  maxZoom: 19,\n}\nconst ESRI_SATELLITE: LeafletTilePreset = {\n  url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',\n  attribution: ESRI_ATTR,\n  maxZoom: 19,\n}\n\nexport const LEAFLET_TILES: Record<Exclude<LeafletMapVariant, 'default' | 'muted'>, LeafletTilePreset> = {\n  streets: OSM_STANDARD,\n  standard: OSM_STANDARD,\n  outdoors: {\n    url: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',\n    attribution: TOPO_ATTR,\n    subdomains: 'abc',\n    maxZoom: 17,\n  },\n  light: ESRI_LIGHT,\n  dark: ESRI_DARK,\n  satellite: ESRI_SATELLITE,\n  'satellite-streets': {\n    ...ESRI_SATELLITE,\n    overlayUrl:\n      'https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',\n  },\n  'navigation-day': ESRI_STREETS,\n  'navigation-night': ESRI_DARK,\n}\n\n/** Theme-aware default tiles: Esri light/dark canvas following the app theme. */\nexport const LEAFLET_THEME_TILES = { light: ESRI_LIGHT, dark: ESRI_DARK }\n\nexport const leafletMapVariants = cva('relative size-full overflow-hidden bg-muted isolate', {\n  variants: {\n    variant: {\n      default: '',\n      muted: '',\n      streets: '',\n      outdoors: '',\n      light: '',\n      dark: '',\n      satellite: '',\n      'satellite-streets': '',\n      'navigation-day': '',\n      'navigation-night': '',\n      standard: '',\n    },\n    size: {\n      default: 'h-96 w-full',\n      sm: 'h-64 w-full',\n      lg: 'h-[500px] w-full',\n      xl: 'h-[650px] w-full',\n      full: 'size-full',\n    },\n  },\n  defaultVariants: {\n    variant: 'default',\n  },\n})\n\nexport type LeafletMapVariants = VariantProps<typeof leafletMapVariants>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/leaflet-map.variants.ts"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/index.ts",
      "content": "import type LeafletMap from './LeafletMap.vue'\n\nexport { default as LeafletMap } from './LeafletMap.vue'\n\n/** Handle returned by a template ref on <LeafletMap> — mirrors the `map`\n *  component's MapRef: camera helpers plus `map` / `getMap()` for the raw\n *  Leaflet Map. */\nexport type LeafletMapRef = InstanceType<typeof LeafletMap>\nexport {\n  leafletMapVariants,\n  LEAFLET_TILES,\n  LEAFLET_THEME_TILES,\n  type LeafletMapVariant,\n  type LeafletMapVariants,\n  type LeafletTilePreset,\n} from './leaflet-map.variants'\n\nexport { default as LeafletMarker } from './LeafletMarker.vue'\nexport { default as LeafletPopup } from './LeafletPopup.vue'\nexport { default as LeafletTooltip } from './LeafletTooltip.vue'\nexport { default as LeafletPolyline } from './LeafletPolyline.vue'\nexport { default as LeafletPolygon } from './LeafletPolygon.vue'\nexport { default as LeafletCircle } from './LeafletCircle.vue'\nexport { default as LeafletCircleMarker } from './LeafletCircleMarker.vue'\nexport { default as LeafletGeoJson } from './LeafletGeoJson.vue'\nexport { default as LeafletTileLayer } from './LeafletTileLayer.vue'\n\nexport { loadLeaflet, toLatLng, toLatLngs, toLatLngBounds, useLeafletMap } from './leaflet-context'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/index.ts"
    },
    {
      "path": "packages/registry-vue/components/leaflet-map/leaflet-map.css",
      "content": "/**\n * Leaflet map chrome — re-skins Leaflet's controls, popups, and tooltips to the\n * design-system tokens so they sit naturally on light and dark surfaces.\n * Global (Leaflet renders this chrome outside the component subtree). Import\n * once, e.g. in your root CSS: `@import './components/ui/leaflet-map/leaflet-map.css';`\n */\n.leaflet-container {\n  font: inherit;\n  background: var(--muted);\n}\n.leaflet-bar {\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-sm);\n}\n.leaflet-bar a,\n.leaflet-bar a:hover {\n  background: var(--card);\n  color: var(--foreground);\n  border-bottom-color: var(--border);\n}\n.leaflet-bar a:hover {\n  background: var(--muted);\n}\n.leaflet-bar a.leaflet-disabled {\n  background: var(--card);\n  color: var(--muted-foreground);\n}\n.leaflet-popup-content-wrapper {\n  background: var(--popover);\n  color: var(--popover-foreground);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-md);\n}\n.leaflet-popup-content {\n  margin: var(--spacing-2, 8px) var(--spacing-3, 12px);\n  font: inherit;\n  line-height: 1.5;\n}\n.leaflet-popup-tip {\n  background: var(--popover);\n  border: 1px solid var(--border);\n  box-shadow: none;\n}\n.leaflet-popup-close-button {\n  color: var(--muted-foreground) !important;\n}\n.leaflet-tooltip {\n  background: var(--popover);\n  color: var(--popover-foreground);\n  border: 1px solid var(--border);\n  border-radius: var(--radius);\n  box-shadow: var(--shadow-sm);\n  font: inherit;\n}\n.leaflet-tooltip-top:before {\n  border-top-color: var(--border);\n}\n.leaflet-tooltip-bottom:before {\n  border-bottom-color: var(--border);\n}\n/* `muted` variant — desaturate only the tile pane so overlaid data keeps its\n * colour. */\n[data-slot='leaflet-map'][data-muted='true'] .leaflet-tile-pane {\n  filter: grayscale(55%) contrast(0.92);\n}\n\n/* Custom marker icons: Leaflet wraps our element in its own icon div; keep it\n * unstyled so slot content controls the look. DivIcon forces a 12×12 inline\n * box plus anchor margins — override so the icon sizes to its content and the\n * inner anchor transform does the positioning. */\n.uipkge-leaflet-div-icon {\n  background: transparent;\n  border: none;\n  width: auto !important;\n  height: auto !important;\n  margin: 0 !important;\n}\n\n/* Marker slot content is a real DOM element moved into the icon. It stays\n * hidden while parked inside the component tree, and the anchor transform\n * decides which point of the content sits on the coordinate. */\n.uipkge-leaflet-anchor {\n  display: none;\n}\n.leaflet-marker-icon .uipkge-leaflet-anchor {\n  display: block;\n  /* shrink-to-fit inside the abspos icon resolves to min-content and wraps\n   * labels — force natural content width instead */\n  width: max-content;\n}\n.uipkge-leaflet-anchor[data-anchor='center'] {\n  transform: translate(-50%, -50%);\n}\n.uipkge-leaflet-anchor[data-anchor='top'] {\n  transform: translate(-50%, 0);\n}\n.uipkge-leaflet-anchor[data-anchor='bottom'] {\n  transform: translate(-50%, -100%);\n}\n.uipkge-leaflet-anchor[data-anchor='left'] {\n  transform: translate(0, -50%);\n}\n.uipkge-leaflet-anchor[data-anchor='right'] {\n  transform: translate(-100%, -50%);\n}\n.uipkge-leaflet-anchor[data-anchor='top-left'] {\n  transform: translate(0, 0);\n}\n.uipkge-leaflet-anchor[data-anchor='top-right'] {\n  transform: translate(-100%, 0);\n}\n.uipkge-leaflet-anchor[data-anchor='bottom-left'] {\n  transform: translate(0, -100%);\n}\n.uipkge-leaflet-anchor[data-anchor='bottom-right'] {\n  transform: translate(-100%, -100%);\n}\n\n/* Popup/tooltip slot sources are real DOM moved into the overlay's content\n * node when it opens. */\n.uipkge-leaflet-popup-src,\n.uipkge-leaflet-tooltip-src {\n  display: none;\n}\n.leaflet-popup-content .uipkge-leaflet-popup-src,\n.leaflet-tooltip .uipkge-leaflet-tooltip-src {\n  display: block;\n}\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/leaflet-map/leaflet-map.css"
    }
  ],
  "dependencies": [
    "leaflet",
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "A thin, theme-aware Leaflet wrapper rendering free raster tiles (OpenStreetMap, OpenTopoMap, Esri) — no API key required. Drop LeafletMarker / LeafletPopup / LeafletPolyline / LeafletGeoJson into the slot to build any map — fleet boards, journey maps, store locators. The base style follows light/dark automatically, an opt-in `muted` prop desaturates the tile pane so overlaid data is the only colour, and `@created` hands you the raw L.Map instance for custom layers and fitBounds. The Mapbox counterpart (`map`) adds GL styles, 3D, and globe; this one trades those for zero-key setup.",
  "categories": [
    "data-display"
  ]
}