{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-property-boundary-map",
  "title": "Leaflet Property Boundary Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/leaflet-property-boundary-map/leaflet-property-boundary-map.component.ts",
      "content": "// Cadastral parcel boundary polygons with zoning classes, lot acreage, and assessed valuation\n// on free Esri satellite tiles — no API key. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, ViewChild, signal } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiLeafletMapComponent,\n  UiLeafletMarkerComponent,\n  UiLeafletPolygonComponent,\n} from '@/ui/leaflet-map/leaflet-map.component'\n\nexport interface Parcel {\n  apn: string\n  address: string\n  zoning: string\n  /** Plain-hex zone fill — Leaflet renders paths to SVG/canvas, so CSS vars do not resolve there. */\n  color: string\n  lotSqFt: string\n  acres: number\n  assessedValue: string\n  taxYear: number\n  coords: [number, number]\n  polygon: [number, number][]\n}\n\nexport const DEFAULT_PARCELS: Parcel[] = [\n  {\n    apn: '093-142-018',\n    address: '418 Kingsley Ave, Palo Alto, CA',\n    zoning: 'R-1 (Single Family)',\n    color: '#facc15',\n    lotSqFt: '8,712 sq ft',\n    acres: 0.2,\n    assessedValue: '$3,940,000',\n    taxYear: 2025,\n    coords: [-122.1709, 37.4281],\n    polygon: [\n      [-122.1715, 37.4285],\n      [-122.1703, 37.4285],\n      [-122.1703, 37.4277],\n      [-122.1715, 37.4277],\n      [-122.1715, 37.4285],\n    ],\n  },\n  {\n    apn: '093-142-019',\n    address: '424 Kingsley Ave, Palo Alto, CA',\n    zoning: 'R-1 (Single Family)',\n    color: '#facc15',\n    lotSqFt: '9,580 sq ft',\n    acres: 0.22,\n    assessedValue: '$4,120,000',\n    taxYear: 2025,\n    coords: [-122.1693, 37.4281],\n    polygon: [\n      [-122.1699, 37.4285],\n      [-122.1687, 37.4285],\n      [-122.1687, 37.4277],\n      [-122.1699, 37.4277],\n      [-122.1699, 37.4285],\n    ],\n  },\n  {\n    apn: '093-142-021',\n    address: '510 Lytton Ave, Palo Alto, CA',\n    zoning: 'CS (Service Commercial)',\n    color: '#c084fc',\n    lotSqFt: '21,780 sq ft',\n    acres: 0.5,\n    assessedValue: '$6,850,000',\n    taxYear: 2025,\n    coords: [-122.1699, 37.4268],\n    polygon: [\n      [-122.1708, 37.4273],\n      [-122.169, 37.4273],\n      [-122.169, 37.4263],\n      [-122.1708, 37.4263],\n      [-122.1708, 37.4273],\n    ],\n  },\n  {\n    apn: '093-142-024',\n    address: '777 Bryant Ct, Palo Alto, CA',\n    zoning: 'RM-20 (Multi-Family)',\n    color: '#38bdf8',\n    lotSqFt: '13,068 sq ft',\n    acres: 0.3,\n    assessedValue: '$5,230,000',\n    taxYear: 2025,\n    coords: [-122.1723, 37.4268],\n    polygon: [\n      [-122.173, 37.4273],\n      [-122.1716, 37.4273],\n      [-122.1716, 37.4263],\n      [-122.173, 37.4263],\n      [-122.173, 37.4273],\n    ],\n  },\n  {\n    apn: '093-142-030',\n    address: '250 Hamilton Ave, Palo Alto, CA',\n    zoning: 'PF (Public Facility)',\n    color: '#34d399',\n    lotSqFt: '30,492 sq ft',\n    acres: 0.7,\n    assessedValue: '$8,900,000',\n    taxYear: 2025,\n    coords: [-122.1679, 37.4261],\n    polygon: [\n      [-122.1687, 37.4265],\n      [-122.1671, 37.4265],\n      [-122.1671, 37.4256],\n      [-122.1687, 37.4256],\n      [-122.1687, 37.4265],\n    ],\n  },\n]\n\n// Downtown Palo Alto — stable reference so the map's center watcher never re-fires.\nexport const MAP_CENTER: [number, number] = [-122.1697, 37.4275]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-property-boundary-map, [ui-leaflet-property-boundary-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiLeafletMapComponent,\n    UiLeafletMarkerComponent,\n    UiLeafletPolygonComponent,\n  ],\n  host: {\n    'data-slot': 'leaflet-property-boundary-map',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div class=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n      <ui-leaflet-map variant=\"satellite-streets\" [center]=\"mapCenter\" [zoom]=\"16\" class=\"h-full w-full\">\n        <!-- Cadastral lot polygons, filled by zoning class -->\n        @for (p of parcels; track 'lot-' + p.apn) {\n          <ui-leaflet-polygon\n            [lngLatPath]=\"p.polygon\"\n            [color]=\"p.color\"\n            [weight]=\"activeParcel()?.apn === p.apn ? 3 : 1.5\"\n            [opacity]=\"0.95\"\n            [fill]=\"true\"\n            [fillColor]=\"p.color\"\n            [fillOpacity]=\"activeParcel()?.apn === p.apn ? 0.35 : 0.15\"\n          />\n        }\n\n        @for (p of parcels; track p.apn) {\n          <ui-leaflet-marker [lngLat]=\"p.coords\" anchor=\"center\">\n            <div class=\"group flex cursor-pointer flex-col items-center\" (click)=\"selectParcel(p)\">\n              <span\n                class=\"border-border bg-background/90 rounded border px-1.5 py-0.5 font-mono text-xs font-bold shadow-md\"\n                [class]=\"activeParcel()?.apn === p.apn ? 'text-foreground' : 'text-muted-foreground'\"\n                [style]=\"activeParcel()?.apn === p.apn ? { color: p.color, borderColor: p.color } : null\"\n              >\n                {{ p.apn }}\n              </span>\n            </div>\n          </ui-leaflet-marker>\n        }\n      </ui-leaflet-map>\n    </div>\n\n    <!-- Cadastral Parcel Valuation Card -->\n    @if (activeParcel(); as parcel) {\n      <ui-card class=\"flex flex-col justify-between\">\n        <ui-card-header>\n          <div class=\"flex items-center justify-between\">\n            <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">APN {{ parcel.apn }}</span>\n            <span ui-badge variant=\"secondary\" class=\"gap-1.5 text-xs\">\n              <span class=\"size-2 rounded-full\" [style.backgroundColor]=\"parcel.color\"></span>\n              {{ parcel.zoning }}\n            </span>\n          </div>\n          <ui-card-title class=\"mt-2 text-base font-medium\">{{ parcel.address }}</ui-card-title>\n          <ui-card-description class=\"font-mono text-xs\"\n            >{{ parcel.lotSqFt }} • {{ parcel.acres }} Acres</ui-card-description\n          >\n        </ui-card-header>\n        <ui-card-content class=\"space-y-4\">\n          <div class=\"border-border space-y-2 border-t pt-3 font-mono text-xs\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground\">Assessed Valuation</span>\n              <span class=\"text-foreground text-base font-bold\">{{ parcel.assessedValue }}</span>\n            </div>\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground\">Zoning Code</span>\n              <span class=\"text-foreground\">{{ parcel.zoning }}</span>\n            </div>\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-muted-foreground\">Assessment Roll Year</span>\n              <span class=\"text-foreground\">{{ parcel.taxYear }}</span>\n            </div>\n          </div>\n\n          <div class=\"border-border space-y-1 border-t pt-3\">\n            <div class=\"text-muted-foreground mb-1 font-mono text-xs tracking-wider uppercase\">\n              Adjacent Cadastral Lots\n            </div>\n            @for (p of parcels; track p.apn) {\n              <button type=\"button\" [class]=\"rowClass(p)\" (click)=\"selectParcel(p)\">\n                <span class=\"font-mono\">{{ p.apn }}</span>\n                <span class=\"font-mono text-xs opacity-75\">{{ p.lotSqFt }}</span>\n              </button>\n            }\n          </div>\n\n          <button ui-button variant=\"outline\" size=\"sm\" class=\"w-full font-mono text-xs\" (click)=\"recenter()\">\n            Center Cadastral Lot\n          </button>\n        </ui-card-content>\n      </ui-card>\n    }\n  `,\n})\nexport class UiLeafletPropertyBoundaryMapComponent {\n  @Input() parcels: Parcel[] = DEFAULT_PARCELS\n  @Input('class') className?: string\n\n  @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent\n\n  protected readonly mapCenter = MAP_CENTER\n\n  readonly activeApn = signal(DEFAULT_PARCELS[0]?.apn ?? '')\n\n  get hostClass(): string {\n    return cn('grid gap-4 lg:grid-cols-[1fr_320px]', this.className)\n  }\n\n  activeParcel(): Parcel | undefined {\n    return this.parcels.find((p) => p.apn === this.activeApn()) ?? this.parcels[0]\n  }\n\n  selectParcel(p: Parcel): void {\n    this.activeApn.set(p.apn)\n    this.map?.flyTo({ center: p.coords, zoom: 17 })\n  }\n\n  recenter(): void {\n    const parcel = this.activeParcel()\n    if (parcel) this.selectParcel(parcel)\n  }\n\n  rowClass(p: Parcel): string {\n    return cn(\n      'flex w-full items-center justify-between rounded-md px-2 py-1 text-xs transition-colors',\n      this.activeParcel()?.apn === p.apn\n        ? 'bg-accent text-accent-foreground font-semibold'\n        : 'hover:bg-muted text-muted-foreground',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-property-boundary-map/leaflet-property-boundary-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/leaflet-property-boundary-map/index.ts",
      "content": "export {\n  UiLeafletPropertyBoundaryMapComponent,\n  MAP_CENTER,\n  DEFAULT_PARCELS,\n  type Parcel,\n} from './leaflet-property-boundary-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-property-boundary-map/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/leaflet-map.json"
  ],
  "description": "Cadastral parcel boundary polygons with zoning classes, lot acreage, and assessed valuation on free Esri satellite tiles — no API key.",
  "categories": [
    "real-estate",
    "places",
    "map"
  ]
}