{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-map",
  "title": "Leaflet Map",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletMap.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type * as L from 'leaflet'\n  import type { LeafletMapVariant, LeafletMapVariants } from './leaflet-map.variants'\n  import type { LeafletPosition } from './leaflet-context.svelte'\n\n  export interface LeafletMapProps extends HTMLAttributes<HTMLDivElement> {\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 `tileUrl`. Defaults to the OpenStreetMap credit. */\n    tileAttribution?: string\n    /** Tile subdomains for a custom `tileUrl` ('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    /** Fires with the raw `L.Map` once it is created. */\n    oncreated?: (map: L.Map) => void\n    children?: Snippet\n    ref?: HTMLDivElement | null\n  }\n\n  export interface LeafletFlyToOptions {\n    /** [lng, lat] — Mapbox order. */\n    center?: [number, number]\n    zoom?: number\n    /** Milliseconds (converted to Leaflet's seconds). */\n    duration?: number\n  }\n\n  export interface LeafletViewOptions {\n    center?: [number, number]\n    zoom?: number\n  }\n\n  /** Handle returned by `bind:this` on <LeafletMap> — mirrors the `map`\n   *  component's MapRef: camera helpers plus `getMap()` for the raw\n   *  Leaflet Map. React's `readonly map` property has no Svelte equivalent\n   *  (`bind:this` publishes functions, not live property bindings) —\n   *  `getMap()` covers it. */\n  export interface LeafletMapRef {\n    getMap: () => L.Map | null\n    flyTo: (options?: LeafletFlyToOptions) => void\n    setView: (options?: LeafletViewOptions) => void\n    jumpTo: (options?: LeafletViewOptions) => void\n    /** [[west,south],[east,north]] in [lng, lat], or any Leaflet bounds expression. */\n    fitBounds: (\n      bounds: [[number, number], [number, number]] | L.LatLngBoundsExpression,\n      options?: L.FitBoundsOptions,\n    ) => void\n    panTo: (center: [number, number]) => void\n    zoomIn: () => void\n    zoomOut: () => void\n    resize: () => void\n  }\n</script>\n\n<script 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 children to build any\n   * map — the same composition model as the Mapbox `map` component.\n   *\n   * Not supported (Leaflet has no vector/GL renderer): pitch, bearing, 3D\n   * buildings/terrain, globe projection. Use a `variant` preset or `tileUrl`\n   * for custom raster tiles instead.\n   */\n  import { onDestroy, onMount, untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n  import {\n    defined,\n    fixDefaultLeafletIcon,\n    loadLeaflet,\n    setLeafletMapContext,\n    toLatLng,\n    toLatLngBounds,\n  } from './leaflet-context.svelte'\n  import type { LeafletTilePreset } from './leaflet-map.variants'\n  import {\n    leafletMapVariants,\n    LEAFLET_TILES,\n    LEAFLET_THEME_TILES,\n  } from './leaflet-map.variants'\n  import 'leaflet/dist/leaflet.css'\n  import './leaflet-map.css'\n\n  let {\n    variant = 'default',\n    size,\n    tileUrl,\n    tileAttribution,\n    tileSubdomains,\n    center = [0, 20],\n    zoom = 2,\n    minZoom,\n    maxZoom,\n    navigation = true,\n    navigationPosition = 'bottom-right',\n    fullscreen = false,\n    fullscreenPosition = 'top-right',\n    attribution = true,\n    scrollWheelZoom = true,\n    muted = false,\n    oncreated,\n    children,\n    ref = $bindable(null),\n    class: className,\n    ...restProps\n  }: LeafletMapProps = $props()\n\n  let htmlDark = $state(false)\n  const isMuted = $derived(muted || variant === 'muted')\n\n  const resolvedTiles = $derived.by<LeafletTilePreset>(() => {\n    if (tileUrl) {\n      return {\n        url: tileUrl,\n        attribution:\n          tileAttribution ?? '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors',\n        subdomains: tileSubdomains,\n        maxZoom,\n      }\n    }\n    if (\n      variant &&\n      variant !== 'default' &&\n      variant !== 'muted' &&\n      LEAFLET_TILES[variant as keyof typeof LEAFLET_TILES]\n    ) {\n      return LEAFLET_TILES[variant as keyof typeof LEAFLET_TILES]\n    }\n    return htmlDark ? 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.\n  let mounted = $state(false)\n  let inView = $state(false)\n  let mapEl = $state<HTMLDivElement | null>(null)\n  let mapInstance = $state<L.Map | null>(null)\n  let Lmod: typeof import('leaflet') | null = null\n  let baseLayer: L.TileLayer | null = null\n  let overlayLayer: L.TileLayer | null = null\n  let lastTiles: LeafletTilePreset | null = null\n  let resizeObserver: ResizeObserver | null = null\n  let intersectionObserver: IntersectionObserver | null = null\n  let themeObserver: MutationObserver | null = null\n\n  // Published to LeafletMarker / LeafletPopup / … children.\n  setLeafletMapContext({\n    get map() {\n      return mapInstance\n    },\n  })\n\n  function applyTiles(tiles: LeafletTilePreset) {\n    const m = mapInstance\n    const Ll = Lmod\n    if (!m || !Ll) return\n    baseLayer?.remove()\n    baseLayer = null\n    overlayLayer?.remove()\n    overlayLayer = null\n    baseLayer = Ll.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 = Ll.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.\n  let attributions = $state<string[]>([])\n  let showAttribution = $state(false)\n  function collectAttributions() {\n    const m = mapInstance\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 = [...seen]\n  }\n\n  let canZoomIn = $state(true)\n  let canZoomOut = $state(true)\n  function syncZoomBounds() {\n    const m = mapInstance\n    if (!m) return\n    canZoomIn = m.getZoom() < m.getMaxZoom()\n    canZoomOut = m.getZoom() > m.getMinZoom()\n  }\n\n  function createMap() {\n    const el = mapEl\n    const Ll = Lmod\n    if (!el || !Ll || mapInstance) return\n    fixDefaultLeafletIcon(Ll)\n    const tiles = resolvedTiles\n    const m = Ll.map(\n      el,\n      defined({\n        center: toLatLng(center ?? [0, 20]),\n        zoom,\n        minZoom,\n        maxZoom: maxZoom ?? tiles.maxZoom,\n        zoomControl: false,\n        attributionControl: false,\n        scrollWheelZoom,\n      }),\n    )\n    mapInstance = m\n    lastTiles = tiles\n    applyTiles(tiles)\n    m.on('zoomend', syncZoomBounds)\n    syncZoomBounds()\n    m.on('layeradd layerremove', collectAttributions)\n    collectAttributions()\n    oncreated?.(m)\n  }\n\n  onMount(() => {\n    const root = document.documentElement\n    const syncTheme = () => {\n      htmlDark = root.classList.contains('dark')\n    }\n    syncTheme()\n    themeObserver = new MutationObserver(syncTheme)\n    themeObserver.observe(root, { attributes: true, attributeFilter: ['class'] })\n    mounted = true\n\n    const el = ref\n    if (!el || typeof IntersectionObserver === 'undefined') {\n      inView = true\n    } else {\n      intersectionObserver = new IntersectionObserver(\n        ([entry]) => {\n          inView = 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        mapInstance?.invalidateSize()\n      })\n      resizeObserver.observe(el)\n    }\n\n    const onFullscreenChange = () => {\n      isFullscreen = Boolean(document.fullscreenElement)\n    }\n    document.addEventListener('fullscreenchange', onFullscreenChange)\n\n    return () => {\n      document.removeEventListener('fullscreenchange', onFullscreenChange)\n    }\n  })\n\n  onDestroy(() => {\n    resizeObserver?.disconnect()\n    intersectionObserver?.disconnect()\n    themeObserver?.disconnect()\n    try {\n      mapInstance?.remove()\n    } catch {\n      /* already destroyed */\n    }\n    mapInstance = null\n  })\n\n  // Leaflet module loads lazily once the container is both mounted and in view.\n  $effect(() => {\n    if (!mounted || !inView || mapInstance) return\n    let alive = true\n    void (async () => {\n      const Ll = await loadLeaflet()\n      if (!alive) return\n      Lmod = Ll\n      createMap()\n    })()\n    return () => {\n      alive = false\n    }\n  })\n\n  // Retile on variant / theme / custom-tile changes (skip the initial run —\n  // createMap already applied them).\n  $effect(() => {\n    const tiles = resolvedTiles\n    if (mapInstance && tiles !== lastTiles) {\n      lastTiles = tiles\n      applyTiles(tiles)\n    }\n  })\n\n  // Track the coordinates, not the array: a parent re-render that passes a\n  // fresh `[lng, lat]` literal must not snap the user's pan/zoom back. The map\n  // is read untracked so creation (and any `oncreated` fitBounds) isn't undone.\n  const centerLng = $derived(center?.[0])\n  const centerLat = $derived(center?.[1])\n  $effect(() => {\n    const lng = centerLng\n    const lat = centerLat\n    const z = zoom\n    const m = untrack(() => mapInstance)\n    if (m && lng !== undefined && lat !== undefined) m.setView(toLatLng([lng, lat]), z)\n  })\n\n  $effect(() => {\n    if (!attribution) showAttribution = false\n  })\n\n  $effect(() => {\n    const enabled = scrollWheelZoom\n    if (!mapInstance) return\n    if (enabled) mapInstance.scrollWheelZoom.enable()\n    else mapInstance.scrollWheelZoom.disable()\n  })\n\n  let isFullscreen = $state(false)\n  function toggleFullscreen() {\n    const el = ref\n    if (!el) return\n    if (document.fullscreenElement) void document.exitFullscreen()\n    else void 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.\n  const cornerOrder = ['top-left', 'top-right', 'bottom-left', 'bottom-right'] as const\n  const cornerClasses = $derived<Record<(typeof cornerOrder)[number], 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': attribution && attributions.length ? 'bottom-9 left-3' : 'bottom-3 left-3',\n    'bottom-right': 'bottom-3 right-3',\n  })\n  const navPosition = $derived(navigationPosition ?? 'bottom-right')\n  const fsPosition = $derived(fullscreenPosition ?? 'top-right')\n\n  /** Mapbox-style camera shim — accepts { center: [lng, lat], zoom, duration(ms) }. */\n  export function flyTo(options: LeafletFlyToOptions = {}) {\n    const m = mapInstance\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\n  export function setView(options: LeafletViewOptions = {}) {\n    const m = mapInstance\n    if (!m) return\n    m.setView(options.center ? toLatLng(options.center) : m.getCenter(), options.zoom ?? m.getZoom())\n  }\n\n  export function jumpTo(options: LeafletViewOptions = {}) {\n    const m = mapInstance\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  // `bind:this` on <LeafletMap> hands back these camera helpers plus\n  // `getMap()` for the raw L.Map. It mirrors the `map` component's MapRef.\n  /** The underlying Leaflet Map, or null until it is created. */\n  export function getMap() {\n    return mapInstance\n  }\n  export function fitBounds(\n    bounds: [[number, number], [number, number]] | L.LatLngBoundsExpression,\n    options?: L.FitBoundsOptions,\n  ) {\n    mapInstance?.fitBounds(toLatLngBounds(bounds as [[number, number], [number, number]]), options)\n  }\n  export function panTo(center: [number, number]) {\n    mapInstance?.panTo(toLatLng(center))\n  }\n  export function zoomIn() {\n    mapInstance?.zoomIn()\n  }\n  export function zoomOut() {\n    mapInstance?.zoomOut()\n  }\n  export function resize() {\n    mapInstance?.invalidateSize()\n  }\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"leaflet-map\"\n  data-variant={variant}\n  data-muted={isMuted}\n  class={cn(leafletMapVariants({ variant, ...(size ? { size } : {}) }), className)}\n  {...restProps}\n>\n  <div bind:this={mapEl} class=\"size-full\" hidden={!inView}></div>\n  {#each cornerOrder as corner (corner)}\n    {#if mapInstance && ((navigation && navPosition === corner) || (fullscreen && fsPosition === corner))}\n      <div class=\"absolute z-[1000] flex flex-col gap-2.5 {cornerClasses[corner]}\">\n        {#if navigation && navPosition === corner}\n          <div\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              onclick={() => mapInstance?.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              onclick={() => mapInstance?.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        {/if}\n        {#if fullscreen && fsPosition === corner}\n          <button\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            onclick={toggleFullscreen}\n          >\n            <svg class=\"size-full\" viewBox=\"0 0 29 29\" fill=\"currentColor\" aria-hidden=\"true\">\n              {#if isFullscreen}\n                <path\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              {:else}\n                <path\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              {/if}\n            </svg>\n          </button>\n        {/if}\n      </div>\n    {/if}\n  {/each}\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  {#if mapInstance && attribution && attributions.length}\n    <div class=\"group absolute bottom-3 left-3 z-[1000] flex flex-col items-start gap-1.5\">\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 {showAttribution\n          ? 'visible opacity-100'\n          : 'invisible opacity-0 group-hover:visible group-hover:opacity-100'}\"\n        role=\"note\"\n      >\n        {@html attributions.join(' | ')}\n      </div>\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        onclick={() => (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  {/if}\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletMap.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletMarker.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export type LeafletMarkerAnchor =\n    | 'center'\n    | 'top'\n    | 'bottom'\n    | 'left'\n    | 'right'\n    | 'top-left'\n    | 'top-right'\n    | 'bottom-left'\n    | 'bottom-right'\n\n  export interface LeafletMarkerProps {\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?: LeafletMarkerAnchor\n    draggable?: boolean\n    opacity?: number\n    zIndexOffset?: number\n    title?: string\n    alt?: string\n    /** Custom marker HTML. Omit for Leaflet's default pin. */\n    icon?: Snippet\n    /** Overlay children (`<LeafletPopup>` / `<LeafletTooltip>`) bound to the marker. */\n    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n    onready?: (marker: L.Marker) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { defined, toLatLng, useLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    lngLat,\n    anchor = 'center',\n    draggable,\n    opacity,\n    zIndexOffset,\n    title,\n    alt,\n    icon,\n    children,\n    onclick,\n    onready,\n  }: LeafletMarkerProps = $props()\n\n  let el = $state<HTMLElement | null>(null)\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const options: L.MarkerOptions = defined({\n      interactive: true,\n      draggable,\n      opacity,\n      zIndexOffset,\n      title,\n      alt,\n    })\n    if (icon && el) {\n      options.icon = Ll.divIcon({ className: 'uipkge-leaflet-div-icon', html: el })\n    }\n    const marker = Ll.marker(toLatLng(lngLat), options)\n    marker.on('click', (ev) => onclick?.(ev))\n    return marker\n  })\n\n  $effect(() => {\n    const m = layer.current\n    if (m) onready?.(m)\n  })\n\n  $effect(() => {\n    const v = lngLat\n    if (v) layer.current?.setLatLng(toLatLng(v))\n  })\n\n  $effect(() => {\n    if (opacity !== undefined) layer.current?.setOpacity(opacity)\n  })\n\n  $effect(() => {\n    if (zIndexOffset !== undefined) layer.current?.setZIndexOffset(zIndexOffset)\n  })\n</script>\n\n<div bind:this={el} class=\"uipkge-leaflet-anchor\" data-anchor={anchor}>\n  {#if icon}\n    {@render icon()}\n  {/if}\n</div>\n{#if children}\n  <div class=\"hidden\">{@render children()}</div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletMarker.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletPopup.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletPopupProps {\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    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { onDestroy } from 'svelte'\n  import { defined, loadLeaflet, toLatLng, useLeafletMap, useParentLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    lngLat,\n    maxWidth,\n    minWidth = 200,\n    offset,\n    className,\n    autoClose,\n    closeOnClick,\n    closeButton,\n    keepInView,\n    children,\n  }: LeafletPopupProps = $props()\n\n  const mapCtx = useLeafletMap()\n  const parentLayer = useParentLeafletLayer()\n  let el = $state<HTMLElement | null>(null)\n  let popup: L.Popup | null = null\n  let boundTo: L.Layer | null = null\n\n  $effect(() => {\n    const m = mapCtx.map\n    const layer = parentLayer?.current ?? null\n    const node = el\n    if (!m || !node) return\n    let alive = true\n    void (async () => {\n      const Ll = await loadLeaflet()\n      if (!alive) return\n      const options = defined({ maxWidth, minWidth, offset, className, autoClose, closeOnClick, closeButton, keepInView })\n      if (layer) {\n        // Re-bind if the parent layer instance was recreated.\n        if (boundTo && boundTo !== layer) {\n          try {\n            boundTo.unbindPopup()\n          } catch {\n            /* already gone */\n          }\n        }\n        boundTo = layer\n        layer.bindPopup(node, options)\n        return\n      }\n      if (lngLat && !popup) {\n        popup = Ll.popup(options).setLatLng(toLatLng(lngLat)).setContent(node)\n        popup.openOn(m)\n      }\n    })()\n    return () => {\n      alive = false\n    }\n  })\n\n  onDestroy(() => {\n    try {\n      boundTo?.unbindPopup()\n      popup?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    boundTo = null\n    popup = null\n  })\n</script>\n\n<div bind:this={el} class=\"uipkge-leaflet-popup-src\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletPopup.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletTooltip.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletTooltipProps {\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    children?: Snippet\n  }\n</script>\n\n<script lang=\"ts\">\n  import { onDestroy } from 'svelte'\n  import { defined, loadLeaflet, toLatLng, useLeafletMap, useParentLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    lngLat,\n    offset,\n    direction,\n    permanent,\n    sticky,\n    opacity,\n    className,\n    interactive,\n    children,\n  }: LeafletTooltipProps = $props()\n\n  const mapCtx = useLeafletMap()\n  const parentLayer = useParentLeafletLayer()\n  let el = $state<HTMLElement | null>(null)\n  let tooltip: L.Tooltip | null = null\n  let boundTo: L.Layer | null = null\n\n  $effect(() => {\n    const m = mapCtx.map\n    const layer = parentLayer?.current ?? null\n    const node = el\n    if (!m || !node) return\n    let alive = true\n    void (async () => {\n      const Ll = await loadLeaflet()\n      if (!alive) return\n      const options = defined({ offset, direction, permanent, sticky, opacity, className, interactive })\n      if (layer) {\n        if (boundTo && boundTo !== layer) {\n          try {\n            boundTo.unbindTooltip()\n          } catch {\n            /* already gone */\n          }\n        }\n        boundTo = layer\n        layer.bindTooltip(node, options)\n        return\n      }\n      if (lngLat && !tooltip) {\n        tooltip = Ll.tooltip(options).setLatLng(toLatLng(lngLat)).setContent(node)\n        tooltip.addTo(m)\n      }\n    })()\n    return () => {\n      alive = false\n    }\n  })\n\n  onDestroy(() => {\n    try {\n      boundTo?.unbindTooltip()\n      tooltip?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    boundTo = null\n    tooltip = null\n  })\n</script>\n\n<div bind:this={el} class=\"uipkge-leaflet-tooltip-src\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletTooltip.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletPolyline.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletPolylineProps {\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    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { defined, toLatLngs, useLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    lngLatPath,\n    color,\n    weight,\n    opacity,\n    lineCap,\n    lineJoin,\n    dashArray,\n    dashOffset,\n    smoothFactor,\n    noClip,\n    className,\n    children,\n    onclick,\n  }: LeafletPolylineProps = $props()\n\n  const pathOptions = () =>\n    defined({\n      color,\n      weight,\n      opacity,\n      lineCap,\n      lineJoin,\n      dashArray,\n      dashOffset,\n      smoothFactor,\n      noClip,\n      className,\n      interactive: true,\n    } as L.PolylineOptions)\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const line = Ll.polyline(toLatLngs(lngLatPath) as L.LatLngExpression[], pathOptions())\n    line.on('click', (ev) => onclick?.(ev))\n    return line\n  })\n\n  $effect(() => {\n    layer.current?.setLatLngs(toLatLngs(lngLatPath) as L.LatLngExpression[])\n  })\n\n  $effect(() => {\n    // Touch every style prop so the effect re-runs when any of them changes.\n    void [color, weight, opacity, dashArray, dashOffset, lineCap, lineJoin]\n    layer.current?.setStyle(pathOptions())\n  })\n</script>\n\n<div class=\"hidden\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletPolyline.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletPolygon.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletPolygonProps {\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    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { defined, toLatLngs, useLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    lngLatPath,\n    color,\n    weight,\n    opacity,\n    lineCap,\n    lineJoin,\n    dashArray,\n    dashOffset,\n    fill,\n    fillColor,\n    fillOpacity,\n    className,\n    children,\n    onclick,\n  }: LeafletPolygonProps = $props()\n\n  const pathOptions = () =>\n    defined({\n      color,\n      weight,\n      opacity,\n      lineCap,\n      lineJoin,\n      dashArray,\n      dashOffset,\n      fill,\n      fillColor,\n      fillOpacity,\n      className,\n      interactive: true,\n    } as L.PolylineOptions)\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const polygon = Ll.polygon(toLatLngs(lngLatPath) as L.LatLngExpression[], pathOptions())\n    polygon.on('click', (ev) => onclick?.(ev))\n    return polygon\n  })\n\n  $effect(() => {\n    layer.current?.setLatLngs(toLatLngs(lngLatPath) as L.LatLngExpression[])\n  })\n\n  $effect(() => {\n    // Touch every style prop so the effect re-runs when any of them changes.\n    void [color, weight, opacity, fill, fillColor, fillOpacity, dashArray]\n    layer.current?.setStyle(pathOptions())\n  })\n</script>\n\n<div class=\"hidden\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletPolygon.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletCircle.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletCircleProps {\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    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { defined, toLatLng, useLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    center,\n    radius,\n    color,\n    weight,\n    opacity,\n    dashArray,\n    fill,\n    fillColor,\n    fillOpacity,\n    className,\n    children,\n    onclick,\n  }: LeafletCircleProps = $props()\n\n  const pathOptions = () =>\n    defined({\n      color,\n      weight,\n      opacity,\n      dashArray,\n      fill,\n      fillColor,\n      fillOpacity,\n      className,\n      interactive: true,\n    } as L.CircleMarkerOptions)\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const circle = Ll.circle(toLatLng(center), { ...pathOptions(), radius })\n    circle.on('click', (ev) => onclick?.(ev))\n    return circle\n  })\n\n  $effect(() => {\n    if (center) layer.current?.setLatLng(toLatLng(center))\n  })\n\n  $effect(() => {\n    if (radius !== undefined) layer.current?.setRadius(radius)\n  })\n\n  $effect(() => {\n    // Touch every style prop so the effect re-runs when any of them changes.\n    void [color, weight, opacity, fill, fillColor, fillOpacity, dashArray]\n    layer.current?.setStyle(pathOptions())\n  })\n</script>\n\n<div class=\"hidden\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletCircle.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletCircleMarker.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletCircleMarkerProps {\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    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { defined, toLatLng, useLeafletLayer } from './leaflet-context.svelte'\n\n  let {\n    center,\n    radius,\n    color,\n    weight,\n    opacity,\n    dashArray,\n    fill,\n    fillColor,\n    fillOpacity,\n    className,\n    children,\n    onclick,\n  }: LeafletCircleMarkerProps = $props()\n\n  const pathOptions = () =>\n    defined({\n      color,\n      weight,\n      opacity,\n      dashArray,\n      fill,\n      fillColor,\n      fillOpacity,\n      className,\n      interactive: true,\n    } as L.CircleMarkerOptions)\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const marker = Ll.circleMarker(toLatLng(center), { ...pathOptions(), radius })\n    marker.on('click', (ev) => onclick?.(ev))\n    return marker\n  })\n\n  $effect(() => {\n    if (center) layer.current?.setLatLng(toLatLng(center))\n  })\n\n  $effect(() => {\n    if (radius !== undefined) layer.current?.setRadius(radius)\n  })\n\n  $effect(() => {\n    // Touch every style prop so the effect re-runs when any of them changes.\n    void [color, weight, opacity, fill, fillColor, fillOpacity, dashArray]\n    layer.current?.setStyle(pathOptions())\n  })\n</script>\n\n<div class=\"hidden\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletCircleMarker.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletGeoJson.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type * as L from 'leaflet'\n\n  export interface LeafletGeoJsonProps {\n    /** GeoJSON FeatureCollection / Feature / geometry. */\n    geojson: GeoJSON.GeoJSON\n    /** Leaflet GeoJSON options: `style`, `pointToLayer`, `onEachFeature`, `filter`, `coordsToLatLng`. */\n    options?: L.GeoJSONOptions\n    children?: Snippet\n    onclick?: (ev: L.LeafletMouseEvent) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { useLeafletLayer } from './leaflet-context.svelte'\n\n  let { geojson, options, children, onclick }: LeafletGeoJsonProps = $props()\n\n  const layer = useLeafletLayer((map, Ll) => {\n    const gj = Ll.geoJSON(geojson as any, options)\n    gj.on('click', (ev) => onclick?.(ev))\n    return gj\n  })\n\n  $effect(() => {\n    const v = geojson\n    if (!layer.current || !v) return\n    layer.current.clearLayers()\n    layer.current.addData(v as any)\n  })\n</script>\n\n<div class=\"hidden\">{@render children?.()}</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletGeoJson.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/LeafletTileLayer.svelte",
      "content": "<script lang=\"ts\" module>\n  import type * as L from 'leaflet'\n\n  export interface LeafletTileLayerProps {\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</script>\n\n<script lang=\"ts\">\n  import { defined, useLeafletLayer } from './leaflet-context.svelte'\n\n  let { url, attribution, subdomains, minZoom, maxZoom, opacity, zIndex, tms }: LeafletTileLayerProps = $props()\n\n  const layer = useLeafletLayer((map, Ll) =>\n    Ll.tileLayer(\n      url,\n      defined({\n        attribution,\n        subdomains,\n        minZoom,\n        maxZoom,\n        opacity,\n        zIndex,\n        tms,\n      }),\n    ),\n  )\n\n  $effect(() => {\n    if (url) layer.current?.setUrl(url)\n  })\n\n  $effect(() => {\n    if (opacity !== undefined) layer.current?.setOpacity(opacity)\n  })\n\n  $effect(() => {\n    if (zIndex !== undefined) layer.current?.setZIndex(zIndex)\n  })\n</script>\n\n<div class=\"hidden\"></div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/LeafletTileLayer.svelte"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/leaflet-context.svelte.ts",
      "content": "import { getContext, onDestroy, setContext } from 'svelte'\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/** Reactive view of the enclosing `<LeafletMap>` instance. Reads track, so an\n *  `$effect` re-runs when the map is created. */\nexport interface LeafletMapContext {\n  readonly map: L.Map | null\n}\n\n/** Published by `<LeafletMap>` once per instance. Must be called during init. */\nexport function setLeafletMapContext(ctx: LeafletMapContext): void {\n  setContext(LEAFLET_MAP_KEY, ctx)\n}\n\n/** Injects the map context published by the enclosing `<LeafletMap>`. */\nexport function useLeafletMap(): LeafletMapContext {\n  const ctx = getContext<LeafletMapContext>(LEAFLET_MAP_KEY)\n  if (!ctx) {\n    throw new Error('uipkge: Leaflet* components must be rendered inside <LeafletMap>.')\n  }\n  return ctx\n}\n\n/** Reactive view of the nearest ancestor layer a popup/tooltip binds to. */\nexport interface LeafletLayerContext {\n  readonly current: L.Layer | null\n}\n\n/** Reactive view of a layer built by `useLeafletLayer`, keeping its concrete type. */\nexport interface LeafletLayerRef<T extends L.Layer> {\n  readonly current: T | null\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 * Must be called during component init.\n */\nexport function useLeafletLayer<T extends L.Layer>(\n  build: (map: L.Map, leaflet: LeafletModule) => T,\n  opts: { addToMap?: boolean } = {},\n): LeafletLayerRef<T> {\n  const mapCtx = useLeafletMap()\n  let layer = $state<T | null>(null)\n  const ctx: LeafletLayerRef<T> = {\n    get current(): T | null {\n      return layer\n    },\n  }\n  setContext(LEAFLET_LAYER_KEY, ctx satisfies LeafletLayerContext)\n\n  let cancelled = false\n  $effect(() => {\n    const map = mapCtx.map\n    if (!map || layer) return\n    let alive = true\n    void (async () => {\n      const Ll = await loadLeaflet()\n      if (!alive || cancelled || layer) return\n      const instance = build(map, Ll)\n      layer = instance\n      if (opts.addToMap !== false) instance.addTo(map)\n    })()\n    return () => {\n      alive = false\n    }\n  })\n\n  onDestroy(() => {\n    cancelled = true\n    try {\n      layer?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    layer = null\n  })\n\n  return ctx\n}\n\n/** The nearest ancestor layer (marker, polyline, …) a popup/tooltip binds to. */\nexport function useParentLeafletLayer(): LeafletLayerContext | null {\n  return getContext<LeafletLayerContext | 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(Ll: LeafletModule) {\n  if (defaultIconFixed) return\n  defaultIconFixed = true\n  Ll.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": "~/src/lib/components/ui/leaflet-map/leaflet-context.svelte.ts"
    },
    {
      "path": "packages/registry-svelte/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": "~/src/lib/components/ui/leaflet-map/leaflet-map.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/leaflet-map/index.ts",
      "content": "export { default as LeafletMap, type LeafletMapProps, type LeafletMapRef } from './LeafletMap.svelte'\n\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, type LeafletMarkerProps, type LeafletMarkerAnchor } from './LeafletMarker.svelte'\nexport { default as LeafletPopup, type LeafletPopupProps } from './LeafletPopup.svelte'\nexport { default as LeafletTooltip, type LeafletTooltipProps } from './LeafletTooltip.svelte'\nexport { default as LeafletPolyline, type LeafletPolylineProps } from './LeafletPolyline.svelte'\nexport { default as LeafletPolygon, type LeafletPolygonProps } from './LeafletPolygon.svelte'\nexport { default as LeafletCircle, type LeafletCircleProps } from './LeafletCircle.svelte'\nexport { default as LeafletCircleMarker, type LeafletCircleMarkerProps } from './LeafletCircleMarker.svelte'\nexport { default as LeafletGeoJson, type LeafletGeoJsonProps } from './LeafletGeoJson.svelte'\nexport { default as LeafletTileLayer, type LeafletTileLayerProps } from './LeafletTileLayer.svelte'\n\nexport {\n  loadLeaflet,\n  toLatLng,\n  toLatLngs,\n  toLatLngBounds,\n  useLeafletMap,\n  type LeafletPosition,\n} from './leaflet-context.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/leaflet-map/index.ts"
    },
    {
      "path": "packages/registry-svelte/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": "~/src/lib/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 children 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 `oncreated` hands you the raw L.Map instance for custom layers and fitBounds.",
  "categories": [
    "data-display"
  ]
}