/** * — meeting point creation modal for rMaps. * Dispatches 'meeting-create' CustomEvent with { name, lat, lng, emoji } detail. * Dispatches 'modal-close' on dismiss. */ const MODAL_STYLE = `position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);`; const MEETING_EMOJIS = ["\u{1F4CD}", "\u{2B50}", "\u{1F3E0}", "\u{1F37D}", "\u{26FA}", "\u{1F3AF}", "\u{1F680}", "\u{1F33F}", "\u{26A1}", "\u{1F48E}"]; class MapMeetingModal extends HTMLElement { private _centerLat = 0; private _centerLng = 0; private _myLat: number | null = null; private _myLng: number | null = null; private _selectedEmoji = "\u{1F4CD}"; private _searchResults: { display_name: string; lat: string; lon: string }[] = []; set center(v: { lat: number; lng: number }) { this._centerLat = v.lat; this._centerLng = v.lng; } set myLocation(v: { lat: number; lng: number } | null) { if (v) { this._myLat = v.lat; this._myLng = v.lng; } else { this._myLat = null; this._myLng = null; } } connectedCallback() { this.render(); } private esc(s: string): string { const d = document.createElement("div"); d.textContent = s || ""; return d.innerHTML; } private close() { this.dispatchEvent(new CustomEvent("modal-close", { bubbles: true, composed: true })); this.remove(); } private render() { this.style.cssText = MODAL_STYLE; this.innerHTML = `
\u{1F4CD} Set Meeting Point
${MEETING_EMOJIS.map((e, i) => ``).join("")}
Map center: ${this._centerLat.toFixed(5)}, ${this._centerLng.toFixed(5)}
`; // Close this.querySelector("#m-close")?.addEventListener("click", () => this.close()); this.addEventListener("click", (e) => { if (e.target === this) this.close(); }); // Emoji picker this.querySelectorAll(".m-emoji").forEach(btn => { btn.addEventListener("click", () => { this._selectedEmoji = (btn as HTMLElement).dataset.emoji!; this.querySelectorAll(".m-emoji").forEach(b => (b as HTMLElement).style.borderColor = "var(--rs-border)"); (btn as HTMLElement).style.borderColor = "#4f46e5"; }); }); // GPS this.querySelector("#m-gps")?.addEventListener("click", () => { const content = this.querySelector("#m-loc-content")!; if (this._myLat !== null && this._myLng !== null) { (this.querySelector("#m-lat") as HTMLInputElement).value = String(this._myLat); (this.querySelector("#m-lng") as HTMLInputElement).value = String(this._myLng); content.innerHTML = `
\u2713 GPS: ${this._myLat!.toFixed(5)}, ${this._myLng!.toFixed(5)}
`; } else { content.innerHTML = `
Share your location first
`; } }); // Search this.querySelector("#m-search")?.addEventListener("click", () => { const content = this.querySelector("#m-loc-content")!; content.innerHTML = `
`; this.querySelector("#m-addr-go")?.addEventListener("click", async () => { const q = (this.querySelector("#m-addr") as HTMLInputElement).value.trim(); if (!q) return; const sr = this.querySelector("#m-sr")!; sr.innerHTML = '
Searching...
'; try { const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(q)}&limit=5`, { headers: { "User-Agent": "rMaps/1.0" }, signal: AbortSignal.timeout(5000), }); this._searchResults = await res.json(); sr.innerHTML = this._searchResults.length ? this._searchResults.map((r, i) => `
${this.esc(r.display_name?.substring(0, 80))}
`).join("") : '
No results
'; sr.querySelectorAll("[data-sr]").forEach(el => { el.addEventListener("click", () => { const r = this._searchResults[parseInt((el as HTMLElement).dataset.sr!, 10)]; (this.querySelector("#m-lat") as HTMLInputElement).value = r.lat; (this.querySelector("#m-lng") as HTMLInputElement).value = r.lon; sr.querySelectorAll("[data-sr]").forEach(e => (e as HTMLElement).style.borderColor = "transparent"); (el as HTMLElement).style.borderColor = "#4f46e5"; }); }); } catch { sr.innerHTML = '
Search failed
'; } }); }); // Manual this.querySelector("#m-manual")?.addEventListener("click", () => { const content = this.querySelector("#m-loc-content")!; content.innerHTML = `
`; this.querySelector("#m-mlat")?.addEventListener("input", (e) => { (this.querySelector("#m-lat") as HTMLInputElement).value = (e.target as HTMLInputElement).value; }); this.querySelector("#m-mlng")?.addEventListener("input", (e) => { (this.querySelector("#m-lng") as HTMLInputElement).value = (e.target as HTMLInputElement).value; }); }); // Create this.querySelector("#m-create")?.addEventListener("click", () => { const name = (this.querySelector("#m-name") as HTMLInputElement).value.trim() || "Meeting point"; const lat = parseFloat((this.querySelector("#m-lat") as HTMLInputElement).value); const lng = parseFloat((this.querySelector("#m-lng") as HTMLInputElement).value); if (isNaN(lat) || isNaN(lng)) return; this.dispatchEvent(new CustomEvent("meeting-create", { detail: { name, lat, lng, emoji: this._selectedEmoji }, bubbles: true, composed: true, })); this.close(); }); } } customElements.define("map-meeting-modal", MapMeetingModal); export { MapMeetingModal };