import { type HTMLAttributes, type ReactNode } from 'react'; import { type VariantProps } from 'class-variance-authority'; export interface MapLatLng { lat: number; lng: number; } export interface MapBounds { north: number; south: number; east: number; west: number; } export interface MapBoundsChangeDetails { /** False for initial load, resizing, fitting and imperative camera moves. */ userInteraction: boolean; } /** * A point to fit the camera to. `id` is its identity (a result id): it * decides whether a new set only added points to the old one. Without * it the coordinates stand in, so two results at one address count as * one. */ export interface MapFitPoint extends MapLatLng { id?: string; } export interface MapMarker { /** Stable id — used for keyed re-rendering and `onMarkerClick`. */ id: string; /** Position in lat/lng. */ position: MapLatLng; /** Optional accessible label — falls back to the id. */ label?: string; } export interface MapViewHandle { /** Pan the map to a new centre without changing zoom. */ panTo: (position: MapLatLng) => void; /** Set zoom (typical range 1–20). */ setZoom: (zoom: number) => void; /** Programmatically select a marker (fires `onMarkerClick`). */ selectMarker: (id: string | null) => void; /** * True once the consent gate has been passed. The map may still be * loading: the three actions above stay silent no-ops until * `getCenter` returns a value. */ isConsentGranted: () => boolean; /** Live map centre, or `null` until the map has loaded. */ getCenter: () => MapLatLng | null; /** * Live zoom level, or `null` until the map has loaded. * Google clamps to the map type's range, so this is not necessarily * the value last passed to `setZoom`. */ getZoom: () => number | null; /** * The controlled `selectedMarkerId` prop as the host currently holds * it. `selectMarker` only proposes a selection — the host is free to * ignore it, and this is the only way to see whether it did. */ getSelectedMarkerId: () => string | null; /** Number of markers passed to the component. Count only, no ids. */ getMarkerCount: () => number; } export interface MapViewProps extends Omit, 'onClick'>, VariantProps { /** Opaque instance id — emitted as `data-component-id`. */ id?: string; /** Google Maps JS API key. Consumer-owned; never cached by the kit. */ apiKey: string; /** Map centre (lat/lng). Required so the consent placeholder can also use it. */ center: MapLatLng; /** Initial zoom. Default 15 (city-block detail, matches booking site). */ zoom?: number; /** * Markers to render. The first marker auto-selects when no * `selectedMarkerId` is given. */ markers?: MapMarker[]; /** Controlled selection — fires `onMarkerClick` with the new id (or null on map click). */ selectedMarkerId?: string | null; /** Fires when a marker is clicked, or `null` when the map background is clicked. */ onMarkerClick?: (id: string | null) => void; /** * The visible viewport once the map settles, including its first bounds. * Fires only when bounds change. `userInteraction` is true after patient * input that pans or zooms, never for a kit-initiated camera move. * Silent until the consented map has loaded. */ onBoundsChange?: (bounds: MapBounds, details: MapBoundsChangeDetails) => void; /** * GDPR consent gate. When `false` (default) the component renders a * placeholder with a translated CTA — the Maps JS API is **not** * loaded. Flip to `true` once the consumer has captured opt-in. */ consentGranted?: boolean; /** Fires when the consumer clicks the "Load map" CTA in the placeholder. */ onConsentRequest?: () => void; /** * Optional address string shown in the consent placeholder so the * surface is useful before the map loads. Also used to build the * fallback "Open in Google Maps" link target. */ address?: string; /** * Cloud-styled-map id. When set, takes precedence over the JSON * `styles` array and disables the theme-bridge re-fire. Use when * you've provisioned a styled map in Google Cloud Console. */ mapId?: string; /** * Override the consent-placeholder description text. The default * (`t('mapView.consentDescription')`) is a generic IP-disclosure * line. Consumers operating under specific GDPR / ePrivacy * disclosures (jurisdiction, named controller, policy link) should * supply their own text via this prop. Pass any `ReactNode` — a * paragraph, a localised `` instance, etc. */ consentDescription?: ReactNode; /** * Replace the kit-rendered "Load map" button with custom CTA chrome. * Use when the consent dialog needs a policy link, a specific * jurisdiction phrasing, or a different surface (Tooltip-wrapped * button, link to a settings page, etc.). When omitted the kit * renders its own `