import { type GeoJsonPosition } from "@trackunit/geo-json-utils"; import type { CategoryKey, ControlConfig } from "../../controls/types"; import type { ClusterConfig, ClusterInfo, ClusterRenderConfig, FitParticipation, MarkerLayerHandle, RenderConfig, ResolutionContext } from "../types"; import { type ViewportResolutionContext } from "./resolveServerClusters"; /** * Options for the `useMarkers` hook. * * Two generics, both inferred from usage: * - `TItem` from `data` * - `TCluster` from `cluster.data` (server-side) or defaults to `ClusterInfo` (client-side / no clustering) */ export type UseMarkersOptions = Readonly<{ /** Unique ID for this layer */ id: string; /** Human-readable name */ name: string; /** Source data array */ data: ReadonlyArray; /** Extract position from item. Return null for items without position. */ getPosition: (item: TItem) => GeoJsonPosition | null; /** Extract unique ID from item */ getId: (item: TItem) => string; /** * Clustering strategy. Omit to disable clustering. * - mode: "client" -- adapter clusters for you (Mapbox native, Supercluster, etc.) * - mode: "server" -- clusters are pre-computed, TCluster inferred from cluster.data */ cluster?: ClusterConfig; /** How markers are rendered (symbol, dom, or adaptive) */ markerRender: RenderConfig; /** * How clusters are rendered. Only relevant when `cluster` is provided. * For server-side clusters, the render function receives the full TCluster. * For client-side clusters, it receives ClusterInfo (adapter-produced). * * Per ADR-0016, the render callback's second argument is * `ClusterRenderState` carrying `memberItems` cross-referenced from * `data` (DOM mode only — symbol mode receives `memberItems: null`). */ clusterRender?: ClusterRenderConfig; /** * Controls to contribute to the map UI, keyed by semantic area. * Omit entirely (or omit a key) to contribute nothing to that area. */ controls?: Partial>>; /** Whether the layer is currently loading its initial data. Omit for static (non-loading) layers. */ loading?: boolean; /** Controls whether this layer's bounds are included in fit-to-content operations. Default: `"all"` */ fitParticipation?: FitParticipation; /** * Per-cluster entity-shape decision (ADR-0016). * * Called per server cluster with `memberItems` cross-referenced from `data` * via `getMarkerIds`. `memberItems` is `null` when any member ID is missing * from the `data` index — in that case `"expand"` silently falls back to * `"keep"`. Omit to leave existing behaviour unchanged. * * Expansion is expand-only (`TCluster` is preserved); the cluster feature * is removed when `"expand"` is returned with non-null `memberItems`. Member * features are not synthesized by the hook — they appear in the marker * feature collection because the consumer keeps the corresponding items in * `data` (the optimistic-fetch pattern described in ADR-0016). */ resolveGroups?: (cluster: TCluster, memberItems: ReadonlyArray | null, ctx: ResolutionContext) => "keep" | "expand"; /** * Live map state forwarded to every `resolveGroups` call. * * When omitted, `zoom`, `markersInViewport`, and `viewportBounds` default * to `0`, `0`, and `null`. Provide this whenever `resolveGroups` is used so * the callback receives real viewport signals instead of zeros. */ resolutionContext?: ViewportResolutionContext; }>; /** * Return type of `useMarkers`. * Extends `MarkerLayerHandle` with convenience count properties. */ export interface UseMarkersReturn extends MarkerLayerHandle { /** All positioned items (excludes items where getPosition returned null) */ readonly positionedCount: number; /** Total items including unpositioned */ readonly totalCount: number; /** Number of clusters (0 when clustering is disabled or data is empty) */ readonly clusterCount: number; } /** * `useMarkers` -- creates a marker layer handle from arbitrary data. * * Generic over: * - `TItem`: the data item type (inferred from `data`) * - `TCluster`: the cluster data type (inferred from `cluster.data` for server-side, * defaults to `ClusterInfo` for client-side or no clustering) * * @example * ```tsx * // Server-side clusters with adaptive rendering: high-importance assets * // are promoted to DOM while the rest stay as fast WebGL symbols. * const fleet = useMarkers({ * id: "fleet", * name: "Fleet Assets", * data: assets, * getPosition: (a) => a.location, * getId: (a) => a.id, * cluster: { * mode: "server", * data: serverClusters, * getPosition: (c) => c.center, * getId: (c) => c.clusterId, * getMarkerIds: (c) => c.assetIds, * getCount: (c) => c.count, * getBbox: (c) => c.bbox, * }, * markerRender: { * mode: "adaptive", * anchor: "center", * resolveMode: (a) => (a.importance >= 8 ? "dom" : "symbol"), * render: (a, state) => { * if (state.medium === "symbol") return { color: statusColor(a.status) }; * return ; * }, * }, * clusterRender: { * mode: "dom", * anchor: "center", * render: (cluster, state) => , * }, * }); * ``` */ export declare const useMarkers: (options: UseMarkersOptions) => UseMarkersReturn;