/** * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Project: Groupe Ka / Ka Maps * * Tiny typed event hub. All MapLibre listeners are centralized in KaMap * and re-emitted here so app components never attach raw map listeners * (and cleanup happens in exactly one place). */ export type KaEventMap = { /** Camera settled after user interaction or animation. * `byUser` distingue le geste (drag, molette, pincement, boutons de * navigation) d'un mouvement programmé (fitBounds, easeTo interne) — * c'est ce qui permet de « respecter l'intention de l'utilisateur ». */ moveend: { center: { lat: number; lng: number }; zoom: number; byUser: boolean }; /** Viewport diverged from the last searched area. */ searchAreaDirty: { dirty: boolean }; /** A property was selected (click) or deselected (null). */ select: { propertyId: string | null; origin: "map" | "app" }; /** Hover changed (desktop only). */ hover: { propertyId: string | null }; /** A cluster was clicked; the map is easing to expansion zoom. */ clusterExpand: { count: number }; /** Data finished loading into the property source. */ data: { count: number; totalCount?: number }; /** Loading state of the bounds query pipeline. */ loading: { loading: boolean }; /** Non-fatal error (tiles, query…) the app may surface. */ error: { scope: "tiles" | "query" | "style"; error: unknown }; /** Le spotlight bâtiment (focusBuilding) a trouvé son empreinte 3D. */ buildingFocus: { found: boolean }; /** Outil de dessin : état courant (en cours de tracé et/ou polygone posé). * `polygon` est un anneau [lng, lat][] fermé implicitement (≥ 3 sommets). */ draw: { polygon: [number, number][] | null; drawing: boolean }; /** Survol d'une bulle de cluster — fourchette de prix + position écran. */ clusterHover: { info: { count: number; min: number | null; max: number | null; x: number; y: number } | null; }; }; type Handler = (payload: T) => void; export class KaEventHub { private handlers = new Map>>(); on( event: K, handler: Handler, ): () => void { let set = this.handlers.get(event); if (!set) { set = new Set(); this.handlers.set(event, set); } set.add(handler as Handler); return () => set?.delete(handler as Handler); } emit(event: K, payload: KaEventMap[K]): void { const set = this.handlers.get(event); if (!set) return; for (const handler of set) (handler as Handler)(payload); } clear(): void { this.handlers.clear(); } }