/** * Location Map Component * Displays a location on an OpenStreetMap map using Leaflet */ import { escapeHTML } from '../utils/helpers.js' const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/search' class LocationMap extends HTMLElement { constructor() { super() this.map = null this.marker = null this.leafletLoaded = false } static get observedAttributes() { return ['lat', 'lng', 'name', 'postal-code', 'country'] } async connectedCallback() { await this.loadLeaflet() this.render() await this.initMap() } disconnectedCallback() { if (this.map) { this.map.remove() this.map = null } } attributeChangedCallback() { if (this.map) { this.updateMap() } } async loadLeaflet() { if (this.leafletLoaded || window.L) { this.leafletLoaded = true return } // Load Leaflet CSS const css = document.createElement('link') css.rel = 'stylesheet' css.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' css.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=' css.crossOrigin = '' document.head.appendChild(css) // Load Leaflet JS await new Promise((resolve, reject) => { const script = document.createElement('script') script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=' script.crossOrigin = '' script.onload = resolve script.onerror = reject document.head.appendChild(script) }) this.leafletLoaded = true } render() { const name = this.getAttribute('name') || '' const postalCode = this.getAttribute('postal-code') || '' const country = this.getAttribute('country') || '' const locationText = [postalCode, name, country].filter(Boolean).join(', ') this.innerHTML = /* html */`
Standort konnte nicht geladen werden
' return } const mapContainer = this.querySelector('#map') if (!mapContainer || !window.L) return this.map = L.map(mapContainer, { scrollWheelZoom: false, dragging: !L.Browser.mobile, attributionControl: false }).setView(coords, 13) // Custom attribution (OSM required, no Leaflet branding) L.control.attribution({ prefix: false }).addAttribution('© OpenStreetMap').addTo(this.map) L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '' }).addTo(this.map) this.marker = L.marker(coords).addTo(this.map) // Invalidate size after render (fixes grey tiles issue) setTimeout(() => { this.map?.invalidateSize() }, 100) } async geocodeLocation() { const name = this.getAttribute('name') || '' const postalCode = this.getAttribute('postal-code') || '' const country = this.getAttribute('country') || '' const query = [postalCode, name, country].filter(Boolean).join(' ') if (!query) return null try { const params = new URLSearchParams({ q: query, format: 'json', limit: '1' }) const response = await fetch(`${NOMINATIM_URL}?${params}`, { headers: { 'User-Agent': 'kashilo.com/1.0' } }) const results = await response.json() if (results.length > 0) { return [parseFloat(results[0].lat), parseFloat(results[0].lon)] } } catch (error) { console.error('Geocoding failed:', error) } return null } async updateMap() { if (!this.map) return const coords = await this.geocodeLocation() if (coords) { this.map.setView(coords, 13) if (this.marker) { this.marker.setLatLng(coords) } } } } customElements.define('location-map', LocationMap) const style = document.createElement('style') style.textContent = /* css */` location-map { display: block; } location-map .location-map-container { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; } location-map .location-map-header { display: flex; align-items: center; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); background: var(--color-bg-secondary); border-bottom: 1px solid var(--color-border); } location-map .location-icon { font-size: 1rem; } location-map .location-text { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); } location-map .location-map { height: 200px; background: var(--color-bg-tertiary); } location-map .map-error { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--color-text-muted); font-size: var(--font-size-sm); } /* Fix Leaflet styles for dark mode */ location-map .leaflet-container { background: var(--color-bg-tertiary); } location-map .leaflet-control-attribution { background: var(--color-bg); color: var(--color-text-muted); font-size: 10px; } location-map .leaflet-control-attribution a { color: var(--color-primary); } ` document.head.appendChild(style) export { LocationMap }