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 `