import type { Map as MapLibreMap, MapMouseEvent, Marker as MapLibreMarker, } from 'maplibre-gl'; /** Subset of maplibre-gl used by the web MapPinSelector. */ export interface MapRuntime { Map: new ( options: ConstructorParameters[0] ) => MapLibreMap; Marker: new ( options?: ConstructorParameters[0] ) => MapLibreMarker; AttributionControl?: new ( options?: ConstructorParameters< NonNullable >[0] ) => import('maplibre-gl').AttributionControl; NavigationControl?: new ( options?: ConstructorParameters< NonNullable >[0] ) => import('maplibre-gl').NavigationControl; } export type { MapLibreMap, MapLibreMarker, MapMouseEvent }; /** * How the host provides MapLibre GL JS. * - `bundled`: import from `maplibre-gl` (default — Next.js, Vite apps with the peer installed) * - `external`: read from `window.maplibregl` (CDN script loaded before the widget, e.g. Shopify) */ export type MapEmbedMode = 'bundled' | 'external'; /** Carto Positron — default basemap for standard web embeds. */ export const DEFAULT_MAP_STYLE_URL = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'; /** OpenFreeMap Positron — free, no API key (used when Carto credentials are unavailable). */ export const OPENFREEMAP_POSITRON_STYLE_URL = 'https://tiles.openfreemap.org/styles/positron'; const MAPLIBRE_WAIT_MS = 8_000; const MAPLIBRE_RETRY_MS = 100; declare global { interface Window { maplibregl?: MapRuntime; } } export function getExternalMapRuntime(): MapRuntime | null { if (typeof window === 'undefined') return null; const maplibre = window.maplibregl; if ( !maplibre || typeof maplibre.Map !== 'function' || typeof maplibre.Marker !== 'function' ) { return null; } return maplibre; } /** * Poll `window.maplibregl` until available or timeout (for async CDN loads). * Returns a cancel function for useEffect cleanup. */ export function waitForExternalMapRuntime( onReady: (runtime: MapRuntime) => void, onUnavailable: () => void, waitMs: number = MAPLIBRE_WAIT_MS, retryMs: number = MAPLIBRE_RETRY_MS ): () => void { const startedAt = Date.now(); let retryTimer: number | null = null; let cancelled = false; const tryGet = () => { if (cancelled) return; const runtime = getExternalMapRuntime(); if (runtime) { onReady(runtime); return; } if (Date.now() - startedAt >= waitMs) { onUnavailable(); return; } retryTimer = window.setTimeout(tryGet, retryMs); }; tryGet(); return () => { cancelled = true; if (retryTimer !== null) window.clearTimeout(retryTimer); }; }