{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-map",
  "title": "Leaflet Map",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/leaflet-map/leaflet-map.component.ts",
      "content": "import {\n  AfterViewInit,\n  ChangeDetectionStrategy,\n  Component,\n  Directive,\n  ElementRef,\n  EventEmitter,\n  Input,\n  OnChanges,\n  OnDestroy,\n  Output,\n  SimpleChanges,\n  ViewChild,\n  booleanAttribute,\n  forwardRef,\n  inject,\n  signal,\n} from '@angular/core'\nimport type * as L from 'leaflet'\nimport { cn } from '@/lib/utils'\nimport {\n  LEAFLET_TILES,\n  LEAFLET_THEME_TILES,\n  leafletMapVariants,\n  type LeafletMapVariant,\n  type LeafletMapVariants,\n  type LeafletTilePreset,\n} from './leaflet-map.variants'\nimport {\n  defined,\n  fixDefaultLeafletIcon,\n  loadLeaflet,\n  toLatLng,\n  toLatLngBounds,\n  toLatLngs,\n  type LeafletModule,\n  type LeafletPosition,\n} from './leaflet-context'\nimport { ensureLeafletStyles } from './leaflet-map.styles'\n\nexport type LeafletMapSize = NonNullable<LeafletMapVariants['size']>\n\nexport type LeafletMarkerAnchor =\n  'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\ntype MapCallback = (map: L.Map, Lmod: LeafletModule) => void\n\nconst CORNER_ORDER: LeafletPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\n\n/**\n * Angular port of the UIPKGE LeafletMap: a thin, theme-aware Leaflet wrapper rendering free\n * raster tiles (no API key). Standalone. Class strings come from shared `leafletMapVariants`\n * and the chrome markup (zoom group, fullscreen button, ⓘ attribution) is the React markup.\n *\n * Styles: the React item imports `leaflet/dist/leaflet.css` + `leaflet-map.css`. Here both\n * live in `leaflet-map.styles.ts` and the map injects them into `<head>` once on construction\n * (`<style data-uipkge-leaflet>`), so an installed item works with no angular.json edits.\n * Already loading `leaflet/dist/leaflet.css` globally is harmless (identical rules).\n *\n * Children draw themselves once the map exists and follow their inputs, like React:\n * - `<ui-leaflet-marker>`: projected content becomes a custom div-icon (the real DOM nodes are\n *   moved into the icon, so bindings and listeners keep working); `anchor` picks which point\n *   of that content sits on the coordinate. With no content it uses Leaflet's default pin.\n * - `<ui-leaflet-popup>`: binds to its parent marker / path (or opens standalone at `lngLat`);\n *   its content is moved into the popup, bindings stay live.\n * - `<ui-leaflet-tooltip>`: same, but a hover tooltip.\n * - `<ui-leaflet-polyline>`, `<ui-leaflet-polygon>`, `<ui-leaflet-circle>`,\n *   `<ui-leaflet-circle-marker>` (pixel radius), `<ui-leaflet-geojson>`, `<ui-leaflet-tile-layer>`.\n * Use `created` (the raw L.Map) for anything else.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-map, [ui-leaflet-map]',\n  standalone: true,\n  host: {\n    '[attr.data-slot]': '\"leaflet-map\"',\n    '[attr.data-variant]': 'variant',\n    '[attr.data-muted]': 'isMuted',\n    '[attr.data-uipkge]': '\"\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div #mapEl class=\"size-full\"></div>\n    @for (corner of cornerOrder; track corner) {\n      @if (showZoomAt(corner) || showFullscreenAt(corner)) {\n        <div [class]=\"cornerClass(corner)\">\n          @if (showZoomAt(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                (click)=\"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)=\"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          }\n          @if (showFullscreenAt(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              (click)=\"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                }\n              </svg>\n            </button>\n          }\n        </div>\n      }\n    }\n    @if (mapReady() && attribution && attributions().length > 0) {\n      <div class=\"group absolute bottom-3 left-3 z-[1000] flex flex-col items-start gap-1.5\">\n        <div role=\"note\" [class]=\"attributionClass()\" [innerHTML]=\"attributions().join(' | ')\"></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          [attr.aria-expanded]=\"showAttribution()\"\n          (click)=\"showAttribution.set(!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    }\n    <ng-content />\n  `,\n})\nexport class UiLeafletMapComponent implements AfterViewInit, OnChanges, OnDestroy {\n  @Input() variant: LeafletMapVariant = 'default'\n  @Input() size?: LeafletMapSize\n  /** Custom raster tile URL template — overrides `variant`. */\n  @Input() tileUrl?: string\n  @Input() tileAttribution?: string\n  @Input() tileSubdomains?: string | string[]\n  /** Initial [lng, lat] — Mapbox order, matching the `map` component. */\n  @Input() center: [number, number] = [0, 20]\n  @Input() zoom = 2\n  @Input() minZoom?: number\n  @Input() maxZoom?: number\n  /** Show the zoom +/- control. */\n  @Input({ transform: booleanAttribute }) navigation = true\n  @Input() navigationPosition: LeafletPosition = 'bottom-right'\n  /** Show the HTML5 fullscreen toggle button. */\n  @Input({ transform: booleanAttribute }) fullscreen = false\n  @Input() fullscreenPosition: LeafletPosition = 'top-right'\n  /** Show tile credits behind a ⓘ button (hover/tap). Keep on — OSM/Esri tiles require attribution. */\n  @Input({ transform: booleanAttribute }) attribution = true\n  @Input({ transform: booleanAttribute }) scrollWheelZoom = true\n  @Input({ transform: booleanAttribute }) muted = false\n  @Input('class') className?: string\n\n  @Output() created = new EventEmitter<L.Map>()\n\n  @ViewChild('mapEl', { static: false }) mapEl?: ElementRef<HTMLDivElement>\n\n  readonly cornerOrder = CORNER_ORDER\n  readonly mapReady = signal(false)\n  readonly canZoomIn = signal(true)\n  readonly canZoomOut = signal(true)\n  readonly isFullscreen = signal(false)\n  readonly attributions = signal<string[]>([])\n  readonly showAttribution = signal(false)\n\n  private map: L.Map | null = null\n  private Lmod: LeafletModule | null = null\n  private baseLayer: L.TileLayer | null = null\n  private overlayLayer: L.TileLayer | null = null\n  private themeObserver: MutationObserver | null = null\n  private resizeObserver: ResizeObserver | null = null\n  private intersectionObserver: IntersectionObserver | null = null\n  private readonly mapCallbacks = new Set<MapCallback>()\n  private destroyed = false\n  private creating = false\n  private dark = false\n  private readonly onFullscreenChange = () => this.isFullscreen.set(Boolean(document.fullscreenElement))\n\n  constructor() {\n    // No inject() here: the map must stay constructible with plain `new` (host-display spec).\n    ensureLeafletStyles()\n  }\n\n  /** The component host (the map element's parent), fullscreen / in-view target. */\n  private get hostElement(): HTMLElement | null {\n    return this.mapEl?.nativeElement.parentElement ?? null\n  }\n\n  get isMuted(): boolean {\n    return this.muted || this.variant === 'muted'\n  }\n\n  get hostClass(): string {\n    return cn(\n      'block',\n      leafletMapVariants({ variant: this.variant, ...(this.size ? { size: this.size } : {}) }),\n      this.className,\n    )\n  }\n\n  showZoomAt(corner: LeafletPosition): boolean {\n    return this.mapReady() && this.navigation && (this.navigationPosition ?? 'bottom-right') === corner\n  }\n\n  showFullscreenAt(corner: LeafletPosition): boolean {\n    return this.mapReady() && this.fullscreen && (this.fullscreenPosition ?? 'top-right') === corner\n  }\n\n  /** Corner stack placement; bottom-left sits above the ⓘ button when credits are shown. */\n  cornerClass(corner: LeafletPosition): string {\n    let placement: string\n    switch (corner) {\n      case 'top-left':\n        placement = 'left-3 top-3'\n        break\n      case 'top-right':\n        placement = 'right-3 top-3'\n        break\n      case 'bottom-left':\n        placement = this.attribution && this.attributions().length ? 'bottom-9 left-3' : 'bottom-3 left-3'\n        break\n      default:\n        placement = 'bottom-3 right-3'\n    }\n    return cn('absolute z-[1000] flex flex-col gap-2.5', placement)\n  }\n\n  attributionClass(): string {\n    return cn(\n      '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      this.showAttribution()\n        ? 'visible opacity-100'\n        : 'invisible opacity-0 group-hover:visible group-hover:opacity-100',\n    )\n  }\n\n  /** Resolve the tile preset: explicit `tileUrl` wins, then the variant preset, then theme-aware light/dark. */\n  resolveTiles(): LeafletTilePreset {\n    if (this.tileUrl) {\n      return {\n        url: this.tileUrl,\n        attribution:\n          this.tileAttribution ??\n          '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors',\n        subdomains: this.tileSubdomains,\n        maxZoom: this.maxZoom,\n      }\n    }\n    if (this.variant && this.variant !== 'default' && this.variant !== 'muted') {\n      const preset = LEAFLET_TILES[this.variant as keyof typeof LEAFLET_TILES]\n      if (preset) return preset\n    }\n    return this.dark ? LEAFLET_THEME_TILES.dark : LEAFLET_THEME_TILES.light\n  }\n\n  getMap(): L.Map | null {\n    return this.map\n  }\n\n  /**\n   * Runs `cb` with the map once it exists (immediately if it already does). Returns an\n   * unsubscribe for callers destroyed before the map is created. Used by the child layers.\n   */\n  whenMap(cb: MapCallback): () => void {\n    if (this.map && this.Lmod) {\n      cb(this.map, this.Lmod)\n      return () => {}\n    }\n    this.mapCallbacks.add(cb)\n    return () => this.mapCallbacks.delete(cb)\n  }\n\n  /** Mapbox-style camera shim — accepts { center: [lng, lat], zoom, duration(ms) }. */\n  flyTo(options: { center?: [number, number]; zoom?: number; duration?: number } = {}): void {\n    const m = this.map\n    if (!m) return\n    m.flyTo(options.center ? toLatLng(options.center) : m.getCenter(), options.zoom ?? m.getZoom(), {\n      duration: (options.duration ?? 800) / 1000,\n    })\n  }\n\n  setView(options: { center?: [number, number]; zoom?: number } = {}): void {\n    const m = this.map\n    if (!m) return\n    m.setView(options.center ? toLatLng(options.center) : m.getCenter(), options.zoom ?? m.getZoom())\n  }\n\n  jumpTo(options: { center?: [number, number]; zoom?: number } = {}): void {\n    const m = this.map\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  fitBounds(\n    bounds: [[number, number], [number, number]] | L.LatLngBoundsExpression,\n    options?: L.FitBoundsOptions,\n  ): void {\n    this.map?.fitBounds(toLatLngBounds(bounds as [[number, number], [number, number]]), options)\n  }\n\n  panTo(center: [number, number]): void {\n    this.map?.panTo(toLatLng(center))\n  }\n\n  zoomIn(): void {\n    this.map?.zoomIn()\n  }\n\n  zoomOut(): void {\n    this.map?.zoomOut()\n  }\n\n  resize(): void {\n    this.map?.invalidateSize()\n  }\n\n  toggleFullscreen(): void {\n    const el = this.hostElement\n    if (!el) return\n    if (document.fullscreenElement) void document.exitFullscreen()\n    else void el.requestFullscreen?.()\n  }\n\n  ngAfterViewInit(): void {\n    if (typeof document === 'undefined' || typeof window === 'undefined') return\n    const root = document.documentElement\n    const syncTheme = () => {\n      const wasDark = this.dark\n      this.dark = root.classList.contains('dark')\n      if (wasDark !== this.dark && this.map) this.applyTiles(this.resolveTiles())\n    }\n    syncTheme()\n    this.themeObserver = new MutationObserver(syncTheme)\n    this.themeObserver.observe(root, { attributes: true, attributeFilter: ['class'] })\n    document.addEventListener('fullscreenchange', this.onFullscreenChange)\n    const el = this.mapEl?.nativeElement\n    if (typeof ResizeObserver !== 'undefined' && el) {\n      // Next frame: resizing the map inside the callback changes layout again, which the browser\n      // reports as a \"ResizeObserver loop\" error (logged as ERROR by Angular's global listeners).\n      this.resizeObserver = new ResizeObserver(() => requestAnimationFrame(() => this.map?.invalidateSize()))\n      this.resizeObserver.observe(el)\n    }\n    // Like React: only create the map once it scrolls near the viewport.\n    const host = this.hostElement\n    if (host && typeof IntersectionObserver !== 'undefined') {\n      this.intersectionObserver = new IntersectionObserver(\n        ([entry]) => {\n          if (entry?.isIntersecting) void this.createMap()\n        },\n        { rootMargin: '160px', threshold: 0.01 },\n      )\n      this.intersectionObserver.observe(host)\n    } else {\n      void this.createMap()\n    }\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (changes['attribution'] && !this.attribution) this.showAttribution.set(false)\n    if (!this.map || !this.Lmod) return\n    // Compare coordinates, not array identity: a parent that rebuilds the\n    // `[lng, lat]` array each change-detection pass must not snap the view back.\n    const prev = changes['center']?.previousValue as [number, number] | undefined\n    const centerMoved =\n      !!changes['center'] && !(prev && prev[0] === this.center?.[0] && prev[1] === this.center?.[1])\n    if (centerMoved || changes['zoom']) {\n      this.map.setView(toLatLng(this.center), this.zoom)\n    }\n    if (changes['variant'] || changes['tileUrl'] || changes['tileAttribution'] || changes['tileSubdomains']) {\n      this.applyTiles(this.resolveTiles())\n    }\n    if (changes['scrollWheelZoom']) {\n      if (this.scrollWheelZoom) this.map.scrollWheelZoom.enable()\n      else this.map.scrollWheelZoom.disable()\n    }\n  }\n\n  ngOnDestroy(): void {\n    this.destroyed = true\n    this.mapCallbacks.clear()\n    this.intersectionObserver?.disconnect()\n    this.intersectionObserver = null\n    this.resizeObserver?.disconnect()\n    this.resizeObserver = null\n    this.themeObserver?.disconnect()\n    this.themeObserver = null\n    if (typeof document !== 'undefined') document.removeEventListener('fullscreenchange', this.onFullscreenChange)\n    try {\n      this.map?.remove()\n    } catch {\n      /* already destroyed */\n    }\n    this.map = null\n  }\n\n  private async createMap(): Promise<void> {\n    const el = this.mapEl?.nativeElement\n    if (!el || this.map || this.creating) return\n    this.creating = true\n    const Lmod = await loadLeaflet()\n    if (this.destroyed || this.map) return\n    this.intersectionObserver?.disconnect()\n    this.intersectionObserver = null\n    this.Lmod = Lmod\n    fixDefaultLeafletIcon(Lmod)\n    const tiles = this.resolveTiles()\n    const m = Lmod.map(\n      el,\n      defined({\n        center: toLatLng(this.center ?? [0, 20]),\n        zoom: this.zoom,\n        minZoom: this.minZoom,\n        maxZoom: this.maxZoom ?? tiles.maxZoom,\n        zoomControl: false,\n        attributionControl: false,\n        scrollWheelZoom: this.scrollWheelZoom,\n      }),\n    )\n    this.map = m\n    this.applyTiles(tiles)\n    m.on('zoomend', () => this.syncZoomBounds())\n    this.syncZoomBounds()\n    m.on('layeradd layerremove', () => this.collectAttributions())\n    this.collectAttributions()\n    this.mapReady.set(true)\n    const pending = [...this.mapCallbacks]\n    this.mapCallbacks.clear()\n    for (const cb of pending) cb(m, Lmod)\n    this.created.emit(m)\n  }\n\n  private syncZoomBounds(): void {\n    const m = this.map\n    if (!m) return\n    this.canZoomIn.set(m.getZoom() < m.getMaxZoom())\n    this.canZoomOut.set(m.getZoom() > m.getMinZoom())\n  }\n\n  /** Attribution strings from every layer (base tiles, overlays, tile-layer children), deduped. */\n  private collectAttributions(): void {\n    const m = this.map\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    this.attributions.set([...seen])\n  }\n\n  private applyTiles(tiles: LeafletTilePreset): void {\n    const m = this.map\n    const Lmod = this.Lmod\n    if (!m || !Lmod) return\n    this.baseLayer?.remove()\n    this.baseLayer = null\n    this.overlayLayer?.remove()\n    this.overlayLayer = null\n    this.baseLayer = Lmod.tileLayer(\n      tiles.url,\n      defined({\n        attribution: tiles.attribution,\n        subdomains: tiles.subdomains,\n        maxZoom: tiles.maxZoom ?? 19,\n      }),\n    )\n    this.baseLayer.addTo(m)\n    if (tiles.overlayUrl) {\n      this.overlayLayer = Lmod.tileLayer(tiles.overlayUrl, { maxZoom: tiles.maxZoom ?? 19 })\n      this.overlayLayer.addTo(m)\n    }\n  }\n}\n\nexport { leafletMapVariants, LEAFLET_TILES, LEAFLET_THEME_TILES, type LeafletMapVariant, type LeafletMapVariants }\n\n/**\n * Base for children that own a Leaflet layer: waits for the enclosing map, adds the layer,\n * removes it on destroy, and publishes it so a nested `<ui-leaflet-popup>` can bind (React's\n * LeafletLayerContext).\n */\n@Directive()\nexport abstract class LeafletLayerHost<T extends L.Layer = L.Layer> implements AfterViewInit, OnDestroy {\n  protected readonly mapHost = inject(UiLeafletMapComponent, { optional: true })\n  protected layer: T | null = null\n  private readonly layerCallbacks = new Set<(layer: T) => void>()\n  private offMap: (() => void) | null = null\n\n  protected abstract build(Lmod: LeafletModule): T\n\n  getLayer(): T | null {\n    return this.layer\n  }\n\n  /** Runs `cb` with the layer once built (immediately if it already is). */\n  whenLayer(cb: (layer: T) => void): () => void {\n    if (this.layer) {\n      cb(this.layer)\n      return () => {}\n    }\n    this.layerCallbacks.add(cb)\n    return () => this.layerCallbacks.delete(cb)\n  }\n\n  ngAfterViewInit(): void {\n    this.offMap =\n      this.mapHost?.whenMap((map, Lmod) => {\n        const layer = this.build(Lmod)\n        layer.addTo(map)\n        this.layer = layer\n        this.onLayerAdded(layer)\n        const pending = [...this.layerCallbacks]\n        this.layerCallbacks.clear()\n        for (const cb of pending) cb(layer)\n      }) ?? null\n  }\n\n  ngOnDestroy(): void {\n    this.offMap?.()\n    this.offMap = null\n    this.layerCallbacks.clear()\n    try {\n      this.layer?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    this.layer = null\n  }\n\n  protected onLayerAdded(_layer: T): void {}\n}\n\n/**\n * Marker at `[lng, lat]` (Mapbox order). Projected content (anything but a nested\n * `<ui-leaflet-popup>` / `<ui-leaflet-tooltip>`) becomes a custom div-icon: the nodes are moved into a\n * `.uipkge-leaflet-anchor` element, so bindings and listeners keep working, and `anchor`\n * decides which point of the content sits on the coordinate. No content: Leaflet's default pin.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-marker, [ui-leaflet-marker]',\n  standalone: true,\n  // Content is parked here until the map moves it into the marker icon (React renders nothing).\n  host: { hidden: '' },\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletMarkerComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletMarkerComponent extends LeafletLayerHost<L.Marker> implements OnChanges {\n  readonly el = inject<ElementRef<HTMLElement>>(ElementRef)\n\n  /** [lng, lat] — Mapbox order. */\n  @Input() lngLat: [number, number] = [0, 0]\n  /** Which edge/corner of the marker content sits on the coordinate. */\n  @Input() anchor: LeafletMarkerAnchor = 'center'\n  @Input() draggable?: boolean\n  @Input() opacity?: number\n  @Input() zIndexOffset?: number\n  @Input() title?: string\n  @Input() alt?: string\n  @Output() ready = new EventEmitter<L.Marker>()\n  /** React `onClick` / Vue `click` on the marker (named `layerClick` — plain `click` would shadow the host's DOM event). */\n  @Output() layerClick = new EventEmitter<L.LeafletMouseEvent>()\n\n  /** The `.uipkge-leaflet-anchor` icon element holding the projected content (null for the default pin). */\n  iconEl: HTMLElement | null = null\n\n  buildOptions(): L.MarkerOptions {\n    return defined({\n      interactive: true,\n      draggable: this.draggable,\n      opacity: this.opacity,\n      zIndexOffset: this.zIndexOffset,\n      title: this.title,\n      alt: this.alt,\n    })\n  }\n\n  protected build(Lmod: LeafletModule): L.Marker {\n    const options = this.buildOptions()\n    const host = this.el.nativeElement\n    const content = [...(host?.childNodes ?? [])].filter(\n      (n) =>\n        !(n instanceof Element && n.matches('ui-leaflet-popup, [ui-leaflet-popup], ui-leaflet-tooltip, [ui-leaflet-tooltip]')),\n    )\n    const hasHtml = content.some((n) => n instanceof Element || (n.nodeType === 3 && n.textContent!.trim() !== ''))\n    if (hasHtml) {\n      const iconEl = document.createElement('div')\n      iconEl.className = 'uipkge-leaflet-anchor'\n      iconEl.dataset['anchor'] = this.anchor ?? 'center'\n      for (const n of content) iconEl.appendChild(n)\n      this.iconEl = iconEl\n      options.icon = Lmod.divIcon({ className: 'uipkge-leaflet-div-icon', html: iconEl })\n    }\n    return Lmod.marker(toLatLng(this.lngLat), options)\n  }\n\n  protected override onLayerAdded(marker: L.Marker): void {\n    marker.on('click', (ev: L.LeafletMouseEvent) => this.layerClick.emit(ev))\n    this.ready.emit(marker)\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    const m = this.layer\n    if (!m) return\n    if (changes['lngLat'] && this.lngLat) m.setLatLng(toLatLng(this.lngLat))\n    if (changes['opacity'] && this.opacity !== undefined) m.setOpacity(this.opacity)\n    if (changes['zIndexOffset'] && this.zIndexOffset !== undefined) m.setZIndexOffset(this.zIndexOffset)\n    if (changes['anchor'] && this.iconEl) this.iconEl.dataset['anchor'] = this.anchor ?? 'center'\n  }\n}\n\n/**\n * Popup bound to the parent marker / path, or opened standalone at `lngLat` on the map.\n * Its content is moved into the popup's content node, so Angular bindings stay live.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-popup, [ui-leaflet-popup]',\n  standalone: true,\n  host: { hidden: '' },\n  template: `<ng-content />`,\n})\nexport class UiLeafletPopupComponent implements AfterViewInit, OnDestroy {\n  private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef, { optional: true })\n  private readonly mapHost = inject(UiLeafletMapComponent, { optional: true })\n  private readonly parent = inject(LeafletLayerHost, { optional: true })\n\n  /** [lng, lat] — standalone popup on the map. Omit inside a layer to bind to it. */\n  @Input() lngLat?: [number, number]\n  @Input() maxWidth?: number\n  /** Minimum popup width. Defaults to 200 — keeps card-style content from collapsing narrow. */\n  @Input() minWidth = 200\n  @Input() offset?: [number, number]\n  @Input() className?: string\n  @Input({ transform: booleanAttribute }) autoClose?: boolean\n  @Input({ transform: booleanAttribute }) closeOnClick?: boolean\n  @Input({ transform: booleanAttribute }) closeButton?: boolean\n  @Input({ transform: booleanAttribute }) keepInView?: boolean\n\n  private off: (() => void) | null = null\n  private boundTo: L.Layer | null = null\n  private popup: L.Popup | null = null\n\n  buildOptions(): L.PopupOptions {\n    return defined({\n      maxWidth: this.maxWidth,\n      minWidth: this.minWidth,\n      offset: this.offset,\n      className: this.className,\n      autoClose: this.autoClose,\n      closeOnClick: this.closeOnClick,\n      closeButton: this.closeButton,\n      keepInView: this.keepInView,\n    } as L.PopupOptions)\n  }\n\n  ngAfterViewInit(): void {\n    if (this.parent) {\n      this.off = this.parent.whenLayer((layer) => {\n        this.boundTo = layer\n        layer.bindPopup(this.takeContent(), this.buildOptions())\n      })\n    } else if (this.lngLat && this.mapHost) {\n      const lngLat = this.lngLat\n      this.off = this.mapHost.whenMap((map, Lmod) => {\n        this.popup = Lmod.popup(this.buildOptions()).setLatLng(toLatLng(lngLat)).setContent(this.takeContent())\n        this.popup.openOn(map)\n      })\n    }\n  }\n\n  /** Moves the projected nodes into a `.uipkge-leaflet-popup-src` node (React portals there). */\n  private takeContent(): HTMLElement {\n    const el = document.createElement('div')\n    el.className = 'uipkge-leaflet-popup-src'\n    for (const n of [...(this.hostEl?.nativeElement?.childNodes ?? [])]) el.appendChild(n)\n    return el\n  }\n\n  ngOnDestroy(): void {\n    this.off?.()\n    this.off = null\n    try {\n      this.boundTo?.unbindPopup()\n      this.popup?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    this.boundTo = null\n    this.popup = null\n  }\n}\n\n/**\n * Tooltip bound to the parent marker / path, or floating standalone at `lngLat` on the map.\n * Its content is moved into the tooltip's content node, so Angular bindings stay live.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-tooltip, [ui-leaflet-tooltip]',\n  standalone: true,\n  host: { hidden: '' },\n  template: `<ng-content />`,\n})\nexport class UiLeafletTooltipComponent implements AfterViewInit, OnDestroy {\n  private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef, { optional: true })\n  private readonly mapHost = inject(UiLeafletMapComponent, { optional: true })\n  private readonly parent = inject(LeafletLayerHost, { optional: true })\n\n  /** [lng, lat] — standalone tooltip on the map. Omit inside a layer to bind to it. */\n  @Input() lngLat?: [number, number]\n  @Input() offset?: [number, number]\n  @Input() direction?: 'top' | 'bottom' | 'left' | 'right' | 'center' | 'auto'\n  @Input({ transform: booleanAttribute }) permanent?: boolean\n  @Input({ transform: booleanAttribute }) sticky?: boolean\n  @Input() opacity?: number\n  @Input() className?: string\n  @Input({ transform: booleanAttribute }) interactive?: boolean\n\n  private off: (() => void) | null = null\n  private boundTo: L.Layer | null = null\n  private tooltip: L.Tooltip | null = null\n\n  buildOptions(): L.TooltipOptions {\n    return defined({\n      offset: this.offset,\n      direction: this.direction,\n      permanent: this.permanent,\n      sticky: this.sticky,\n      opacity: this.opacity,\n      className: this.className,\n      interactive: this.interactive,\n    } as L.TooltipOptions)\n  }\n\n  ngAfterViewInit(): void {\n    if (this.parent) {\n      this.off = this.parent.whenLayer((layer) => {\n        this.boundTo = layer\n        layer.bindTooltip(this.takeContent(), this.buildOptions())\n      })\n    } else if (this.lngLat && this.mapHost) {\n      const lngLat = this.lngLat\n      this.off = this.mapHost.whenMap((map, Lmod) => {\n        this.tooltip = Lmod.tooltip(this.buildOptions()).setLatLng(toLatLng(lngLat)).setContent(this.takeContent())\n        this.tooltip.addTo(map)\n      })\n    }\n  }\n\n  /** Moves the projected nodes into a `.uipkge-leaflet-tooltip-src` node (React portals there). */\n  private takeContent(): HTMLElement {\n    const el = document.createElement('div')\n    el.className = 'uipkge-leaflet-tooltip-src'\n    for (const n of [...(this.hostEl?.nativeElement?.childNodes ?? [])]) el.appendChild(n)\n    return el\n  }\n\n  ngOnDestroy(): void {\n    this.off?.()\n    this.off = null\n    try {\n      this.boundTo?.unbindTooltip()\n      this.tooltip?.remove()\n    } catch {\n      /* map already destroyed */\n    }\n    this.boundTo = null\n    this.tooltip = null\n  }\n}\n\n/** Shared path inputs (React's LeafletPathProps). */\n@Directive()\nabstract class LeafletPathHost<T extends L.Path> extends LeafletLayerHost<T> implements OnChanges {\n  @Input() color?: string\n  @Input() weight?: number\n  @Input() opacity?: number\n  @Input() lineCap?: 'butt' | 'round' | 'square'\n  @Input() lineJoin?: 'miter' | 'round' | 'bevel'\n  @Input() dashArray?: string | number[]\n  @Input() dashOffset?: string\n  @Input() fill?: boolean\n  @Input() fillColor?: string\n  @Input() fillOpacity?: number\n  @Input() className?: string\n  /** React `onClick` / Vue `click` on the path. */\n  @Output() layerClick = new EventEmitter<L.LeafletMouseEvent>()\n\n  protected override onLayerAdded(layer: T): void {\n    layer.on('click', (ev: L.LeafletMouseEvent) => this.layerClick.emit(ev))\n  }\n\n  protected pathOptions(): L.PathOptions {\n    return defined({\n      color: this.color,\n      weight: this.weight,\n      opacity: this.opacity,\n      lineCap: this.lineCap,\n      lineJoin: this.lineJoin,\n      dashArray: this.dashArray,\n      dashOffset: this.dashOffset,\n      fill: this.fill,\n      fillColor: this.fillColor,\n      fillOpacity: this.fillOpacity,\n      className: this.className,\n      interactive: true,\n    } as L.PathOptions)\n  }\n\n  /** Geometry updates (lngLatPath / center / radius); styles are handled here. */\n  protected abstract updateGeometry(layer: T, changes: SimpleChanges): void\n\n  ngOnChanges(changes: SimpleChanges): void {\n    const layer = this.layer\n    if (!layer) return\n    this.updateGeometry(layer, changes)\n    const styleKeys = [\n      'color',\n      'weight',\n      'opacity',\n      'lineCap',\n      'lineJoin',\n      'dashArray',\n      'dashOffset',\n      'fill',\n      'fillColor',\n      'fillOpacity',\n    ]\n    if (styleKeys.some((k) => changes[k])) layer.setStyle(this.pathOptions())\n  }\n}\n\n/** Polyline through `[lng, lat][]` (or `[][]` for multi-part lines). */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-polyline, [ui-leaflet-polyline]',\n  standalone: true,\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletPolylineComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletPolylineComponent extends LeafletPathHost<L.Polyline> {\n  @Input() lngLatPath: [number, number][] | [number, number][][] = []\n  @Input() smoothFactor?: number\n  @Input({ transform: booleanAttribute }) noClip?: boolean\n\n  buildOptions(): L.PolylineOptions {\n    return defined({ ...this.pathOptions(), smoothFactor: this.smoothFactor, noClip: this.noClip } as L.PolylineOptions)\n  }\n\n  protected build(Lmod: LeafletModule): L.Polyline {\n    return Lmod.polyline(toLatLngs(this.lngLatPath) as L.LatLngExpression[], this.buildOptions())\n  }\n\n  protected updateGeometry(layer: L.Polyline, changes: SimpleChanges): void {\n    if (changes['lngLatPath']) layer.setLatLngs(toLatLngs(this.lngLatPath) as L.LatLngExpression[])\n  }\n}\n\n/** Polygon ring(s) as `[lng, lat][]` (or `[][]` for holes/multi). */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-polygon, [ui-leaflet-polygon]',\n  standalone: true,\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletPolygonComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletPolygonComponent extends LeafletPathHost<L.Polygon> {\n  @Input() lngLatPath: [number, number][] | [number, number][][] = []\n\n  buildOptions(): L.PolylineOptions {\n    return this.pathOptions() as L.PolylineOptions\n  }\n\n  protected build(Lmod: LeafletModule): L.Polygon {\n    return Lmod.polygon(toLatLngs(this.lngLatPath) as L.LatLngExpression[], this.buildOptions())\n  }\n\n  protected updateGeometry(layer: L.Polygon, changes: SimpleChanges): void {\n    if (changes['lngLatPath']) layer.setLatLngs(toLatLngs(this.lngLatPath) as L.LatLngExpression[])\n  }\n}\n\n/** Circle at `[lng, lat]` with a radius in meters. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-circle, [ui-leaflet-circle]',\n  standalone: true,\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletCircleComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletCircleComponent extends LeafletPathHost<L.Circle> {\n  /** [lng, lat] — Mapbox order. */\n  @Input() center: [number, number] = [0, 0]\n  /** Radius in meters. */\n  @Input() radius?: number\n\n  buildOptions(): L.CircleMarkerOptions {\n    return this.pathOptions() as L.CircleMarkerOptions\n  }\n\n  protected build(Lmod: LeafletModule): L.Circle {\n    return Lmod.circle(\n      toLatLng(this.center),\n      defined({ ...this.buildOptions(), radius: this.radius }) as L.CircleOptions,\n    )\n  }\n\n  protected updateGeometry(layer: L.Circle, changes: SimpleChanges): void {\n    if (changes['center'] && this.center) layer.setLatLng(toLatLng(this.center))\n    if (changes['radius'] && this.radius !== undefined) layer.setRadius(this.radius)\n  }\n}\n\n/** CircleMarker at `[lng, lat]` with a radius in pixels (stays the same size at any zoom). */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-circle-marker, [ui-leaflet-circle-marker]',\n  standalone: true,\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletCircleMarkerComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletCircleMarkerComponent extends LeafletPathHost<L.CircleMarker> {\n  /** [lng, lat] — Mapbox order. */\n  @Input() center: [number, number] = [0, 0]\n  /** Radius in pixels. */\n  @Input() radius?: number\n\n  buildOptions(): L.CircleMarkerOptions {\n    return this.pathOptions() as L.CircleMarkerOptions\n  }\n\n  protected build(Lmod: LeafletModule): L.CircleMarker {\n    return Lmod.circleMarker(\n      toLatLng(this.center),\n      defined({ ...this.buildOptions(), radius: this.radius }) as L.CircleMarkerOptions,\n    )\n  }\n\n  protected updateGeometry(layer: L.CircleMarker, changes: SimpleChanges): void {\n    if (changes['center'] && this.center) layer.setLatLng(toLatLng(this.center))\n    if (changes['radius'] && this.radius !== undefined) layer.setRadius(this.radius)\n  }\n}\n\n/** GeoJSON overlay: FeatureCollection / Feature / geometry with Leaflet GeoJSON options. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-geojson, [ui-leaflet-geojson]',\n  standalone: true,\n  providers: [{ provide: LeafletLayerHost, useExisting: forwardRef(() => UiLeafletGeoJsonComponent) }],\n  template: `<ng-content />`,\n})\nexport class UiLeafletGeoJsonComponent extends LeafletLayerHost<L.GeoJSON> implements OnChanges {\n  /** GeoJSON FeatureCollection / Feature / geometry. */\n  @Input() geojson!: GeoJSON.GeoJSON\n  /** Leaflet GeoJSON options: `style`, `pointToLayer`, `onEachFeature`, `filter`, `coordsToLatLng`. */\n  @Input() options?: L.GeoJSONOptions\n  /** React `onClick` / Vue `click` on a GeoJSON feature. */\n  @Output() layerClick = new EventEmitter<L.LeafletMouseEvent>()\n\n  protected build(Lmod: LeafletModule): L.GeoJSON {\n    return Lmod.geoJSON(this.geojson as L.GeoJsonObject, this.options)\n  }\n\n  protected override onLayerAdded(layer: L.GeoJSON): void {\n    layer.on('click', (ev: L.LeafletMouseEvent) => this.layerClick.emit(ev))\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    const layer = this.layer\n    if (!layer || !this.geojson) return\n    if (changes['geojson']) {\n      layer.clearLayers()\n      layer.addData(this.geojson as L.GeoJsonObject)\n    }\n  }\n}\n\n/** Extra raster tile layer composited onto the map. */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-tile-layer, [ui-leaflet-tile-layer]',\n  standalone: true,\n  template: ``,\n})\nexport class UiLeafletTileLayerComponent extends LeafletLayerHost<L.TileLayer> implements OnChanges {\n  /** Raster tile URL template ({z}/{x}/{y}, optional {s} subdomains + {r} retina). */\n  @Input() url!: string\n  @Input() attribution?: string\n  @Input() subdomains?: string | string[]\n  @Input() minZoom?: number\n  @Input() maxZoom?: number\n  @Input() opacity?: number\n  @Input() zIndex?: number\n  @Input({ transform: booleanAttribute }) tms?: boolean\n\n  buildOptions(): L.TileLayerOptions {\n    return defined({\n      attribution: this.attribution,\n      subdomains: this.subdomains,\n      minZoom: this.minZoom,\n      maxZoom: this.maxZoom,\n      opacity: this.opacity,\n      zIndex: this.zIndex,\n      tms: this.tms,\n    })\n  }\n\n  protected build(Lmod: LeafletModule): L.TileLayer {\n    return Lmod.tileLayer(this.url, this.buildOptions())\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    const layer = this.layer\n    if (!layer) return\n    if (changes['url'] && this.url) layer.setUrl(this.url)\n    if (changes['opacity'] && this.opacity !== undefined) layer.setOpacity(this.opacity)\n    if (changes['zIndex'] && this.zIndex !== undefined) layer.setZIndex(this.zIndex)\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/leaflet-map/leaflet-map.component.ts"
    },
    {
      "path": "packages/registry-angular/components/leaflet-map/leaflet-context.ts",
      "content": "import { InjectionToken } from '@angular/core'\nimport type * as L from 'leaflet'\n\n/**\n * Angular port of `leaflet-context.ts`. Leaflet touches `window`/`document`\n * at import time, so the module is resolved lazily on the client only via\n * `loadLeaflet()` — never a static top-level import.\n */\n\nexport type LeafletModule = typeof import('leaflet')\n\n/** Published by `<ui-leaflet-map>`; injected by `ui-leaflet-*` children. */\nexport const LEAFLET_MAP = new InjectionToken<L.Map | null>('uipkge-leaflet-map')\n\n/** Nearest ancestor layer a popup/tooltip binds to. */\nexport const LEAFLET_PARENT_LAYER = new InjectionToken<L.Layer | null>('uipkge-leaflet-layer')\n\nlet leafletPromise: Promise<LeafletModule> | null = null\n\n/** Lazily loads Leaflet's ESM build (client only). */\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\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 */\nexport function fixDefaultLeafletIcon(Lmod: LeafletModule): void {\n  if (defaultIconFixed) return\n  defaultIconFixed = true\n  Lmod.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/app/components/ui/leaflet-map/leaflet-context.ts"
    },
    {
      "path": "packages/registry-angular/components/leaflet-map/leaflet-map.styles.ts",
      "content": "/**\n * Stylesheets for the Angular LeafletMap, injected once into <head> by\n * `ensureLeafletStyles()` (called from UiLeafletMapComponent's constructor).\n *\n * Why a TS module and not `styleUrl` / angular.json `styles`: the React item imports\n * `leaflet/dist/leaflet.css` + `./leaflet-map.css` and lets the bundler handle them. Angular\n * has nothing equivalent a copied registry file can rely on (a node_modules path in `styleUrl`\n * is fragile, angular.json edits are manual, and JIT/TestBed cannot resolve `styleUrl` without\n * `compileComponents()`), and the combined ~18 kB would fail Angular's default 8 kB\n * `anyComponentStyle` budget. Both sheets are global anyway: Leaflet renders its panes, popups\n * and marker icons outside any component subtree.\n */\n\n/**\n * Leaflet 1.9.4 core stylesheet (`leaflet/dist/leaflet.css`), vendored verbatim except:\n * relative `images/*` URLs point at the versioned unpkg assets (bundlers cannot resolve them\n * from here), and the `.leaflet-default-icon-path` rule is dropped: with it, L.Icon.Default\n * would prefix the absolute icon URLs set by `fixDefaultLeafletIcon()` with a guessed path.\n *\n * Leaflet: BSD 2-Clause License. Copyright (c) 2010-2023, Volodymyr Agafonkin;\n * Copyright (c) 2010-2011, CloudMade. https://github.com/Leaflet/Leaflet/blob/main/LICENSE\n */\nexport const LEAFLET_CORE_CSS = `/* required styles */\n\n.leaflet-pane,\n.leaflet-tile,\n.leaflet-marker-icon,\n.leaflet-marker-shadow,\n.leaflet-tile-container,\n.leaflet-pane > svg,\n.leaflet-pane > canvas,\n.leaflet-zoom-box,\n.leaflet-image-layer,\n.leaflet-layer {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\t}\n.leaflet-container {\n\toverflow: hidden;\n\t}\n.leaflet-tile,\n.leaflet-marker-icon,\n.leaflet-marker-shadow {\n\t-webkit-user-select: none;\n\t   -moz-user-select: none;\n\t        user-select: none;\n\t  -webkit-user-drag: none;\n\t}\n/* Prevents IE11 from highlighting tiles in blue */\n.leaflet-tile::selection {\n\tbackground: transparent;\n}\n/* Safari renders non-retina tile on retina better with this, but Chrome is worse */\n.leaflet-safari .leaflet-tile {\n\timage-rendering: -webkit-optimize-contrast;\n\t}\n/* hack that prevents hw layers \"stretching\" when loading new tiles */\n.leaflet-safari .leaflet-tile-container {\n\twidth: 1600px;\n\theight: 1600px;\n\t-webkit-transform-origin: 0 0;\n\t}\n.leaflet-marker-icon,\n.leaflet-marker-shadow {\n\tdisplay: block;\n\t}\n/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */\n/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */\n.leaflet-container .leaflet-overlay-pane svg {\n\tmax-width: none !important;\n\tmax-height: none !important;\n\t}\n.leaflet-container .leaflet-marker-pane img,\n.leaflet-container .leaflet-shadow-pane img,\n.leaflet-container .leaflet-tile-pane img,\n.leaflet-container img.leaflet-image-layer,\n.leaflet-container .leaflet-tile {\n\tmax-width: none !important;\n\tmax-height: none !important;\n\twidth: auto;\n\tpadding: 0;\n\t}\n\n.leaflet-container img.leaflet-tile {\n\t/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */\n\tmix-blend-mode: plus-lighter;\n}\n\n.leaflet-container.leaflet-touch-zoom {\n\t-ms-touch-action: pan-x pan-y;\n\ttouch-action: pan-x pan-y;\n\t}\n.leaflet-container.leaflet-touch-drag {\n\t-ms-touch-action: pinch-zoom;\n\t/* Fallback for FF which doesn't support pinch-zoom */\n\ttouch-action: none;\n\ttouch-action: pinch-zoom;\n}\n.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {\n\t-ms-touch-action: none;\n\ttouch-action: none;\n}\n.leaflet-container {\n\t-webkit-tap-highlight-color: transparent;\n}\n.leaflet-container a {\n\t-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);\n}\n.leaflet-tile {\n\tfilter: inherit;\n\tvisibility: hidden;\n\t}\n.leaflet-tile-loaded {\n\tvisibility: inherit;\n\t}\n.leaflet-zoom-box {\n\twidth: 0;\n\theight: 0;\n\t-moz-box-sizing: border-box;\n\t     box-sizing: border-box;\n\tz-index: 800;\n\t}\n/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\n.leaflet-overlay-pane svg {\n\t-moz-user-select: none;\n\t}\n\n.leaflet-pane         { z-index: 400; }\n\n.leaflet-tile-pane    { z-index: 200; }\n.leaflet-overlay-pane { z-index: 400; }\n.leaflet-shadow-pane  { z-index: 500; }\n.leaflet-marker-pane  { z-index: 600; }\n.leaflet-tooltip-pane   { z-index: 650; }\n.leaflet-popup-pane   { z-index: 700; }\n\n.leaflet-map-pane canvas { z-index: 100; }\n.leaflet-map-pane svg    { z-index: 200; }\n\n.leaflet-vml-shape {\n\twidth: 1px;\n\theight: 1px;\n\t}\n.lvml {\n\tbehavior: url(#default#VML);\n\tdisplay: inline-block;\n\tposition: absolute;\n\t}\n\n\n/* control positioning */\n\n.leaflet-control {\n\tposition: relative;\n\tz-index: 800;\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\n\tpointer-events: auto;\n\t}\n.leaflet-top,\n.leaflet-bottom {\n\tposition: absolute;\n\tz-index: 1000;\n\tpointer-events: none;\n\t}\n.leaflet-top {\n\ttop: 0;\n\t}\n.leaflet-right {\n\tright: 0;\n\t}\n.leaflet-bottom {\n\tbottom: 0;\n\t}\n.leaflet-left {\n\tleft: 0;\n\t}\n.leaflet-control {\n\tfloat: left;\n\tclear: both;\n\t}\n.leaflet-right .leaflet-control {\n\tfloat: right;\n\t}\n.leaflet-top .leaflet-control {\n\tmargin-top: 10px;\n\t}\n.leaflet-bottom .leaflet-control {\n\tmargin-bottom: 10px;\n\t}\n.leaflet-left .leaflet-control {\n\tmargin-left: 10px;\n\t}\n.leaflet-right .leaflet-control {\n\tmargin-right: 10px;\n\t}\n\n\n/* zoom and fade animations */\n\n.leaflet-fade-anim .leaflet-popup {\n\topacity: 0;\n\t-webkit-transition: opacity 0.2s linear;\n\t   -moz-transition: opacity 0.2s linear;\n\t        transition: opacity 0.2s linear;\n\t}\n.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\n\topacity: 1;\n\t}\n.leaflet-zoom-animated {\n\t-webkit-transform-origin: 0 0;\n\t    -ms-transform-origin: 0 0;\n\t        transform-origin: 0 0;\n\t}\nsvg.leaflet-zoom-animated {\n\twill-change: transform;\n}\n\n.leaflet-zoom-anim .leaflet-zoom-animated {\n\t-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\n\t   -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\n\t        transition:         transform 0.25s cubic-bezier(0,0,0.25,1);\n\t}\n.leaflet-zoom-anim .leaflet-tile,\n.leaflet-pan-anim .leaflet-tile {\n\t-webkit-transition: none;\n\t   -moz-transition: none;\n\t        transition: none;\n\t}\n\n.leaflet-zoom-anim .leaflet-zoom-hide {\n\tvisibility: hidden;\n\t}\n\n\n/* cursors */\n\n.leaflet-interactive {\n\tcursor: pointer;\n\t}\n.leaflet-grab {\n\tcursor: -webkit-grab;\n\tcursor:    -moz-grab;\n\tcursor:         grab;\n\t}\n.leaflet-crosshair,\n.leaflet-crosshair .leaflet-interactive {\n\tcursor: crosshair;\n\t}\n.leaflet-popup-pane,\n.leaflet-control {\n\tcursor: auto;\n\t}\n.leaflet-dragging .leaflet-grab,\n.leaflet-dragging .leaflet-grab .leaflet-interactive,\n.leaflet-dragging .leaflet-marker-draggable {\n\tcursor: move;\n\tcursor: -webkit-grabbing;\n\tcursor:    -moz-grabbing;\n\tcursor:         grabbing;\n\t}\n\n/* marker & overlays interactivity */\n.leaflet-marker-icon,\n.leaflet-marker-shadow,\n.leaflet-image-layer,\n.leaflet-pane > svg path,\n.leaflet-tile-container {\n\tpointer-events: none;\n\t}\n\n.leaflet-marker-icon.leaflet-interactive,\n.leaflet-image-layer.leaflet-interactive,\n.leaflet-pane > svg path.leaflet-interactive,\nsvg.leaflet-image-layer.leaflet-interactive path {\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\n\tpointer-events: auto;\n\t}\n\n/* visual tweaks */\n\n.leaflet-container {\n\tbackground: #ddd;\n\toutline-offset: 1px;\n\t}\n.leaflet-container a {\n\tcolor: #0078A8;\n\t}\n.leaflet-zoom-box {\n\tborder: 2px dotted #38f;\n\tbackground: rgba(255,255,255,0.5);\n\t}\n\n\n/* general typography */\n.leaflet-container {\n\tfont-family: \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n\tfont-size: 12px;\n\tfont-size: 0.75rem;\n\tline-height: 1.5;\n\t}\n\n\n/* general toolbar styles */\n\n.leaflet-bar {\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.65);\n\tborder-radius: 4px;\n\t}\n.leaflet-bar a {\n\tbackground-color: #fff;\n\tborder-bottom: 1px solid #ccc;\n\twidth: 26px;\n\theight: 26px;\n\tline-height: 26px;\n\tdisplay: block;\n\ttext-align: center;\n\ttext-decoration: none;\n\tcolor: black;\n\t}\n.leaflet-bar a,\n.leaflet-control-layers-toggle {\n\tbackground-position: 50% 50%;\n\tbackground-repeat: no-repeat;\n\tdisplay: block;\n\t}\n.leaflet-bar a:hover,\n.leaflet-bar a:focus {\n\tbackground-color: #f4f4f4;\n\t}\n.leaflet-bar a:first-child {\n\tborder-top-left-radius: 4px;\n\tborder-top-right-radius: 4px;\n\t}\n.leaflet-bar a:last-child {\n\tborder-bottom-left-radius: 4px;\n\tborder-bottom-right-radius: 4px;\n\tborder-bottom: none;\n\t}\n.leaflet-bar a.leaflet-disabled {\n\tcursor: default;\n\tbackground-color: #f4f4f4;\n\tcolor: #bbb;\n\t}\n\n.leaflet-touch .leaflet-bar a {\n\twidth: 30px;\n\theight: 30px;\n\tline-height: 30px;\n\t}\n.leaflet-touch .leaflet-bar a:first-child {\n\tborder-top-left-radius: 2px;\n\tborder-top-right-radius: 2px;\n\t}\n.leaflet-touch .leaflet-bar a:last-child {\n\tborder-bottom-left-radius: 2px;\n\tborder-bottom-right-radius: 2px;\n\t}\n\n/* zoom control */\n\n.leaflet-control-zoom-in,\n.leaflet-control-zoom-out {\n\tfont: bold 18px 'Lucida Console', Monaco, monospace;\n\ttext-indent: 1px;\n\t}\n\n.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  {\n\tfont-size: 22px;\n\t}\n\n\n/* layers control */\n\n.leaflet-control-layers {\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.4);\n\tbackground: #fff;\n\tborder-radius: 5px;\n\t}\n.leaflet-control-layers-toggle {\n\tbackground-image: url(https://unpkg.com/leaflet@1.9.4/dist/images/layers.png);\n\twidth: 36px;\n\theight: 36px;\n\t}\n.leaflet-retina .leaflet-control-layers-toggle {\n\tbackground-image: url(https://unpkg.com/leaflet@1.9.4/dist/images/layers-2x.png);\n\tbackground-size: 26px 26px;\n\t}\n.leaflet-touch .leaflet-control-layers-toggle {\n\twidth: 44px;\n\theight: 44px;\n\t}\n.leaflet-control-layers .leaflet-control-layers-list,\n.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\n\tdisplay: none;\n\t}\n.leaflet-control-layers-expanded .leaflet-control-layers-list {\n\tdisplay: block;\n\tposition: relative;\n\t}\n.leaflet-control-layers-expanded {\n\tpadding: 6px 10px 6px 6px;\n\tcolor: #333;\n\tbackground: #fff;\n\t}\n.leaflet-control-layers-scrollbar {\n\toverflow-y: scroll;\n\toverflow-x: hidden;\n\tpadding-right: 5px;\n\t}\n.leaflet-control-layers-selector {\n\tmargin-top: 2px;\n\tposition: relative;\n\ttop: 1px;\n\t}\n.leaflet-control-layers label {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-size: 1.08333em;\n\t}\n.leaflet-control-layers-separator {\n\theight: 0;\n\tborder-top: 1px solid #ddd;\n\tmargin: 5px -10px 5px -6px;\n\t}\n\n\n\n/* attribution and scale controls */\n\n.leaflet-container .leaflet-control-attribution {\n\tbackground: #fff;\n\tbackground: rgba(255, 255, 255, 0.8);\n\tmargin: 0;\n\t}\n.leaflet-control-attribution,\n.leaflet-control-scale-line {\n\tpadding: 0 5px;\n\tcolor: #333;\n\tline-height: 1.4;\n\t}\n.leaflet-control-attribution a {\n\ttext-decoration: none;\n\t}\n.leaflet-control-attribution a:hover,\n.leaflet-control-attribution a:focus {\n\ttext-decoration: underline;\n\t}\n.leaflet-attribution-flag {\n\tdisplay: inline !important;\n\tvertical-align: baseline !important;\n\twidth: 1em;\n\theight: 0.6669em;\n\t}\n.leaflet-left .leaflet-control-scale {\n\tmargin-left: 5px;\n\t}\n.leaflet-bottom .leaflet-control-scale {\n\tmargin-bottom: 5px;\n\t}\n.leaflet-control-scale-line {\n\tborder: 2px solid #777;\n\tborder-top: none;\n\tline-height: 1.1;\n\tpadding: 2px 5px 1px;\n\twhite-space: nowrap;\n\t-moz-box-sizing: border-box;\n\t     box-sizing: border-box;\n\tbackground: rgba(255, 255, 255, 0.8);\n\ttext-shadow: 1px 1px #fff;\n\t}\n.leaflet-control-scale-line:not(:first-child) {\n\tborder-top: 2px solid #777;\n\tborder-bottom: none;\n\tmargin-top: -2px;\n\t}\n.leaflet-control-scale-line:not(:first-child):not(:last-child) {\n\tborder-bottom: 2px solid #777;\n\t}\n\n.leaflet-touch .leaflet-control-attribution,\n.leaflet-touch .leaflet-control-layers,\n.leaflet-touch .leaflet-bar {\n\tbox-shadow: none;\n\t}\n.leaflet-touch .leaflet-control-layers,\n.leaflet-touch .leaflet-bar {\n\tborder: 2px solid rgba(0,0,0,0.2);\n\tbackground-clip: padding-box;\n\t}\n\n\n/* popup */\n\n.leaflet-popup {\n\tposition: absolute;\n\ttext-align: center;\n\tmargin-bottom: 20px;\n\t}\n.leaflet-popup-content-wrapper {\n\tpadding: 1px;\n\ttext-align: left;\n\tborder-radius: 12px;\n\t}\n.leaflet-popup-content {\n\tmargin: 13px 24px 13px 20px;\n\tline-height: 1.3;\n\tfont-size: 13px;\n\tfont-size: 1.08333em;\n\tmin-height: 1px;\n\t}\n.leaflet-popup-content p {\n\tmargin: 17px 0;\n\tmargin: 1.3em 0;\n\t}\n.leaflet-popup-tip-container {\n\twidth: 40px;\n\theight: 20px;\n\tposition: absolute;\n\tleft: 50%;\n\tmargin-top: -1px;\n\tmargin-left: -20px;\n\toverflow: hidden;\n\tpointer-events: none;\n\t}\n.leaflet-popup-tip {\n\twidth: 17px;\n\theight: 17px;\n\tpadding: 1px;\n\n\tmargin: -10px auto 0;\n\tpointer-events: auto;\n\n\t-webkit-transform: rotate(45deg);\n\t   -moz-transform: rotate(45deg);\n\t    -ms-transform: rotate(45deg);\n\t        transform: rotate(45deg);\n\t}\n.leaflet-popup-content-wrapper,\n.leaflet-popup-tip {\n\tbackground: white;\n\tcolor: #333;\n\tbox-shadow: 0 3px 14px rgba(0,0,0,0.4);\n\t}\n.leaflet-container a.leaflet-popup-close-button {\n\tposition: absolute;\n\ttop: 0;\n\tright: 0;\n\tborder: none;\n\ttext-align: center;\n\twidth: 24px;\n\theight: 24px;\n\tfont: 16px/24px Tahoma, Verdana, sans-serif;\n\tcolor: #757575;\n\ttext-decoration: none;\n\tbackground: transparent;\n\t}\n.leaflet-container a.leaflet-popup-close-button:hover,\n.leaflet-container a.leaflet-popup-close-button:focus {\n\tcolor: #585858;\n\t}\n.leaflet-popup-scrolled {\n\toverflow: auto;\n\t}\n\n.leaflet-oldie .leaflet-popup-content-wrapper {\n\t-ms-zoom: 1;\n\t}\n.leaflet-oldie .leaflet-popup-tip {\n\twidth: 24px;\n\tmargin: 0 auto;\n\n\t-ms-filter: \"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)\";\n\tfilter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\n\t}\n\n.leaflet-oldie .leaflet-control-zoom,\n.leaflet-oldie .leaflet-control-layers,\n.leaflet-oldie .leaflet-popup-content-wrapper,\n.leaflet-oldie .leaflet-popup-tip {\n\tborder: 1px solid #999;\n\t}\n\n\n/* div icon */\n\n.leaflet-div-icon {\n\tbackground: #fff;\n\tborder: 1px solid #666;\n\t}\n\n\n/* Tooltip */\n/* Base styles for the element that has a tooltip */\n.leaflet-tooltip {\n\tposition: absolute;\n\tpadding: 6px;\n\tbackground-color: #fff;\n\tborder: 1px solid #fff;\n\tborder-radius: 3px;\n\tcolor: #222;\n\twhite-space: nowrap;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n\tpointer-events: none;\n\tbox-shadow: 0 1px 3px rgba(0,0,0,0.4);\n\t}\n.leaflet-tooltip.leaflet-interactive {\n\tcursor: pointer;\n\tpointer-events: auto;\n\t}\n.leaflet-tooltip-top:before,\n.leaflet-tooltip-bottom:before,\n.leaflet-tooltip-left:before,\n.leaflet-tooltip-right:before {\n\tposition: absolute;\n\tpointer-events: none;\n\tborder: 6px solid transparent;\n\tbackground: transparent;\n\tcontent: \"\";\n\t}\n\n/* Directions */\n\n.leaflet-tooltip-bottom {\n\tmargin-top: 6px;\n}\n.leaflet-tooltip-top {\n\tmargin-top: -6px;\n}\n.leaflet-tooltip-bottom:before,\n.leaflet-tooltip-top:before {\n\tleft: 50%;\n\tmargin-left: -6px;\n\t}\n.leaflet-tooltip-top:before {\n\tbottom: 0;\n\tmargin-bottom: -12px;\n\tborder-top-color: #fff;\n\t}\n.leaflet-tooltip-bottom:before {\n\ttop: 0;\n\tmargin-top: -12px;\n\tmargin-left: -6px;\n\tborder-bottom-color: #fff;\n\t}\n.leaflet-tooltip-left {\n\tmargin-left: -6px;\n}\n.leaflet-tooltip-right {\n\tmargin-left: 6px;\n}\n.leaflet-tooltip-left:before,\n.leaflet-tooltip-right:before {\n\ttop: 50%;\n\tmargin-top: -6px;\n\t}\n.leaflet-tooltip-left:before {\n\tright: 0;\n\tmargin-right: -12px;\n\tborder-left-color: #fff;\n\t}\n.leaflet-tooltip-right:before {\n\tleft: 0;\n\tmargin-left: -12px;\n\tborder-right-color: #fff;\n\t}\n\n/* Printing */\n\n@media print {\n\t/* Prevent printers from removing background-images of controls. */\n\t.leaflet-control {\n\t\t-webkit-print-color-adjust: exact;\n\t\tprint-color-adjust: exact;\n\t\t}\n\t}\n`\n\n/**\n * Map chrome: re-skins Leaflet's controls, popups and tooltips to the design tokens, plus the\n * custom-marker anchor and popup-source rules. Byte-identical to the React/Vue\n * `leaflet-map.css` (leaflet-map.spec.ts enforces it).\n */\nexport const LEAFLET_MAP_CSS = `/**\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`\n\nconst STYLE_ATTR = 'data-uipkge-leaflet'\n\n/**\n * Appends the Leaflet core + chrome stylesheet to `<head>` once per document (chrome after\n * core, so its token overrides win). Safe alongside an app-level `leaflet/dist/leaflet.css`:\n * the rules are identical. No-op without a DOM (SSR).\n */\nexport function ensureLeafletStyles(doc: Document | null = typeof document === 'undefined' ? null : document): void {\n  if (!doc?.head || doc.head.querySelector(`style[${STYLE_ATTR}]`)) return\n  const style = doc.createElement('style')\n  style.setAttribute(STYLE_ATTR, '')\n  style.textContent = `${LEAFLET_CORE_CSS}\\n${LEAFLET_MAP_CSS}`\n  doc.head.appendChild(style)\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/leaflet-map/leaflet-map.styles.ts"
    },
    {
      "path": "packages/registry-angular/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/app/components/ui/leaflet-map/leaflet-map.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/leaflet-map/index.ts",
      "content": "export {\n  UiLeafletMapComponent,\n  UiLeafletMarkerComponent,\n  UiLeafletPopupComponent,\n  UiLeafletTooltipComponent,\n  UiLeafletPolylineComponent,\n  UiLeafletPolygonComponent,\n  UiLeafletCircleComponent,\n  UiLeafletCircleMarkerComponent,\n  UiLeafletGeoJsonComponent,\n  UiLeafletTileLayerComponent,\n  LeafletLayerHost,\n  leafletMapVariants,\n  LEAFLET_TILES,\n  LEAFLET_THEME_TILES,\n  type LeafletMapVariant,\n  type LeafletMapVariants,\n  type LeafletMapSize,\n  type LeafletMarkerAnchor,\n} from './leaflet-map.component'\nexport {\n  LEAFLET_MAP,\n  LEAFLET_PARENT_LAYER,\n  loadLeaflet,\n  toLatLng,\n  toLatLngs,\n  toLatLngBounds,\n  defined,\n  fixDefaultLeafletIcon,\n  type LeafletModule,\n  type LeafletPosition,\n} from './leaflet-context'\nexport { ensureLeafletStyles, LEAFLET_CORE_CSS, LEAFLET_MAP_CSS } from './leaflet-map.styles'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/leaflet-map/index.ts"
    }
  ],
  "dependencies": [
    "leaflet",
    "class-variance-authority"
  ],
  "devDependencies": [
    "@types/leaflet"
  ],
  "registryDependencies": [],
  "description": "A thin, theme-aware Leaflet wrapper as Angular standalone components (UiLeafletMapComponent + marker / popup / polyline / polygon / circle / tile-layer children) rendering free raster tiles — no API key required. The base style follows light/dark automatically, an opt-in `muted` input desaturates the tile pane, and `created` hands you the raw L.Map instance. Leaflet's stylesheet and the token-skinned map chrome ship in `leaflet-map.styles.ts` and are injected into <head> once — no angular.json edits.",
  "categories": [
    "data-display"
  ]
}