{"version":3,"file":"BrazilStateMap.cjs","names":[],"sources":["../../src/br/BrazilStateMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the municipality-level\n * twin of BrazilMap, prop for prop, plus the `uf` that selects which state's\n * geometry to load. Splitting it differently from BrazilMap would mean two maps with\n * two shapes for the same job.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { loadStateMunicipalities, type StateMunicipalities } from \"./state-geo\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** A municipality identified for selection callbacks. */\nexport interface Municipality {\n    /** 7-digit IBGE code. */\n    id: string;\n    /** Municipality name. */\n    name: string;\n}\n\n/** Data passed to a {@link BrazilStateMapProps.renderTooltip} callback. */\nexport interface BrazilStateMapTooltipData extends Municipality {\n    /** Choropleth value for this municipality, if `values` was provided. */\n    value?: number;\n}\n\nexport interface BrazilStateMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n    /** Federative unit to draw (required). */\n    uf: UF;\n    /** Selected municipality — matched by IBGE `id` or by `name`. Accepts many. */\n    selected?: string | readonly string[] | null;\n    /** Fired when a municipality is clicked. */\n    onSelect?: (municipality: Municipality) => void;\n    /**\n     * Choropleth values keyed by municipality `id` **or** `name`. When set, each\n     * municipality is tinted between `minColor` and `maxColor`.\n     */\n    values?: Record<string, number>;\n    /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n    minColor?: string;\n    /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n    maxColor?: string;\n    /**\n     * Custom value→color scale (from `sequentialScale`/`quantizeScale`). Takes\n     * precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n     */\n    colorScale?: ColorScale;\n    /** Viewport height in pixels. Default: `440`. */\n    height?: number;\n    /** Inner padding in pixels. Default: `12`. */\n    padding?: number;\n    /**\n     * Render each municipality name at its centroid. Off by default — a state\n     * can have hundreds of municipalities and labels overlap badly.\n     */\n    showLabels?: boolean;\n    /** Accessible label. Default: derived from the state name. */\n    label?: string;\n    /** Content while the state geometry is loading. */\n    loadingContent?: ReactNode;\n    /** Show a floating tooltip (name + IBGE code + value) on hover. Default: `true`. */\n    showTooltip?: boolean;\n    /** Override the default tooltip content. */\n    renderTooltip?: (data: BrazilStateMapTooltipData) => ReactNode;\n    /** Point markers to overlay on the state (e.g. addresses, POIs). */\n    markers?: readonly GeoMarker[];\n    /** Fired when a marker is clicked. */\n    onMarkerClick?: (marker: GeoMarker, index: number) => void;\n    /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n    zoomable?: boolean;\n}\n\n/**\n * Clickable submap of a single Brazilian state showing **all its\n * municipalities** as SVG paths — no external tiles or paid API. The state's\n * geometry (~40-70 KB gzip) is loaded lazily per UF, so switching states fetches\n * only what is shown.\n *\n * @example\n * const [city, setCity] = useState<string | null>(null);\n * <BrazilStateMap uf=\"SP\" selected={city} onSelect={(m) => setCity(m.name)} />\n *\n * @example\n * // Choropleth of a metric by municipality name\n * <BrazilStateMap uf=\"RJ\" values={{ \"Rio de Janeiro\": 100, Niterói: 40 }} />\n */\nexport function BrazilStateMap({\n    uf,\n    selected,\n    onSelect,\n    values,\n    minColor = \"#dbeafe\",\n    maxColor = \"#2563eb\",\n    colorScale,\n    height = 440,\n    padding = 12,\n    showLabels = false,\n    label,\n    loadingContent,\n    showTooltip = true,\n    renderTooltip,\n    markers,\n    onMarkerClick,\n    zoomable = false,\n    className,\n    style,\n    ...rest\n}: BrazilStateMapProps) {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const [width, setWidth] = useState<number>(600);\n    const [collection, setCollection] = useState<StateMunicipalities | null>(null);\n    const mapZoom = useMapZoom(zoomable);\n    const { hover, onMove, onLeave } = useMapHover<Municipality>(containerRef);\n\n    useEffect(() => {\n        let active = true;\n        setCollection(null);\n        void loadStateMunicipalities(uf).then((data) => {\n            if (active) setCollection(data);\n        });\n        return () => {\n            active = false;\n        };\n    }, [uf]);\n\n    useEffect(() => {\n        const node = containerRef.current;\n        if (!node || typeof ResizeObserver === \"undefined\") return;\n        const observer = new ResizeObserver((entries) => {\n            const measured = entries[0]?.contentRect.width;\n            if (measured && measured > 0) setWidth(measured);\n        });\n        observer.observe(node);\n        return () => observer.disconnect();\n    }, []);\n\n    const selectedSet = useMemo<Set<string>>(() => {\n        if (!selected) return new Set();\n        return new Set(Array.isArray(selected) ? selected : [selected as string]);\n    }, [selected]);\n\n    const valueRange = useMemo<[number, number] | null>(() => {\n        if (!values) return null;\n        const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n        if (nums.length === 0) return null;\n        return [Math.min(...nums), Math.max(...nums)];\n    }, [values]);\n\n    const shapes = useMemo(() => {\n        if (!collection) return null;\n        const projection = fitProjection(\n            geometriesBounds(collection.features.map((f) => f.geometry)),\n            width,\n            height,\n            { padding },\n        );\n        const items = collection.features.map((feature) => ({\n            id: feature.properties.id,\n            name: feature.properties.name,\n            d: geometryPath(feature.geometry, projection),\n            centroid: geometryCentroid(feature.geometry, projection),\n        }));\n        return { projection, items };\n    }, [collection, width, height, padding]);\n\n    function fillFor(id: string, name: string): string | undefined {\n        const v = values?.[id] ?? values?.[name];\n        if (typeof v !== \"number\") return undefined;\n        if (colorScale) return colorScale(v);\n        if (!valueRange) return undefined;\n        const [min, max] = valueRange;\n        const t = max === min ? 1 : (v - min) / (max - min);\n        return lerpColor(minColor, maxColor, t);\n    }\n\n    // Memoized so hover re-renders don't rebuild hundreds of municipality paths.\n    const cityEls = useMemo(() => {\n        if (!shapes) return null;\n        return shapes.items.map((shape) => {\n            const isSelected = selectedSet.has(shape.id) || selectedSet.has(shape.name);\n            const fillColor = isSelected ? undefined : fillFor(shape.id, shape.name);\n            return (\n                <path\n                    key={shape.id}\n                    className={cn(styles.state, isSelected && styles.selected)}\n                    d={shape.d}\n                    fillRule=\"evenodd\"\n                    style={fillColor ? { fill: fillColor } : undefined}\n                    data-city-id={shape.id}\n                    data-city={shape.name}\n                    tabIndex={onSelect ? 0 : undefined}\n                    role={onSelect ? \"button\" : undefined}\n                    aria-label={shape.name}\n                    aria-pressed={onSelect ? isSelected : undefined}\n                    onClick={\n                        onSelect ? () => onSelect({ id: shape.id, name: shape.name }) : undefined\n                    }\n                    onMouseMove={\n                        showTooltip\n                            ? (e) => onMove({ id: shape.id, name: shape.name }, e)\n                            : undefined\n                    }\n                    onMouseLeave={showTooltip ? onLeave : undefined}\n                    onKeyDown={\n                        onSelect\n                            ? (e) => {\n                                  if (e.key === \"Enter\" || e.key === \" \") {\n                                      e.preventDefault();\n                                      onSelect({ id: shape.id, name: shape.name });\n                                  }\n                              }\n                            : undefined\n                    }\n                />\n            );\n        });\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [\n        shapes,\n        selectedSet,\n        values,\n        colorScale,\n        minColor,\n        maxColor,\n        onSelect,\n        showTooltip,\n        onMove,\n        onLeave,\n    ]);\n\n    const resolvedLabel = label ?? `Municípios de ${getState(uf)?.name ?? uf}`;\n\n    return (\n        <div\n            ref={containerRef}\n            className={cn(styles.map, className)}\n            style={{ height, ...style }}\n            role=\"group\"\n            aria-label={resolvedLabel}\n            {...rest}\n        >\n            {!shapes ? (\n                <div className={styles.loading}>{loadingContent ?? \"Carregando municípios…\"}</div>\n            ) : (\n                <svg\n                    className={styles.svg}\n                    width=\"100%\"\n                    height={height}\n                    viewBox={`0 0 ${width} ${height}`}\n                    preserveAspectRatio=\"xMidYMid meet\"\n                    {...mapZoom.handlers}\n                >\n                    <g transform={mapZoom.transform || undefined}>\n                        {cityEls}\n\n                        {showLabels &&\n                            shapes.items.map((shape) => (\n                                <text\n                                    key={`label-${shape.id}`}\n                                    className={styles.label}\n                                    x={shape.centroid.x}\n                                    y={shape.centroid.y}\n                                    textAnchor=\"middle\"\n                                    dominantBaseline=\"central\"\n                                >\n                                    {shape.name}\n                                </text>\n                            ))}\n\n                        {markers && markers.length > 0 && (\n                            <MapMarkers\n                                projection={shapes.projection}\n                                markers={markers}\n                                onMarkerClick={onMarkerClick}\n                            />\n                        )}\n                    </g>\n                </svg>\n            )}\n\n            {mapZoom.isTransformed && (\n                <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n                    Reset\n                </button>\n            )}\n\n            {showTooltip && hover && (\n                <MapTooltip x={hover.x} y={hover.y}>\n                    {renderTooltip ? (\n                        renderTooltip({\n                            ...hover.item,\n                            value: values?.[hover.item.id] ?? values?.[hover.item.name],\n                        })\n                    ) : (\n                        <>\n                            <div className={styles.tooltipTitle}>{hover.item.name}</div>\n                            <div className={styles.tooltipMeta}>\n                                IBGE {hover.item.id}\n                                {(() => {\n                                    const v = values?.[hover.item.id] ?? values?.[hover.item.name];\n                                    return typeof v === \"number\" ? ` · ${v}` : \"\";\n                                })()}\n                            </div>\n                        </>\n                    )}\n                </MapTooltip>\n            )}\n        </div>\n    );\n}\n"],"mappings":"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"}