import { ChevronDown, Crosshair, LocateFixed, MapPin, Search, Trash2 } from "lucide-react"; import { useId, useLayoutEffect, useRef } from "react"; import { Button } from "@/shared/ui/button"; import { Input } from "@/shared/ui/input"; import { cn } from "@/shared/ui/cn"; import type { WorkbenchGeocoding } from "../hooks/use-workbench-geocoding"; import { MAX_PLACE_SEARCH_RESULTS, type PlaceSearchItem } from "../api/place-search-client"; const RESULT_VIEWPORT_CLASS_NAME = "h-72 min-h-0 max-h-72 overflow-y-auto overscroll-contain pr-1 [scrollbar-gutter:stable] [scrollbar-width:thin]"; function PlaceSummary({ item, index, annotation = item.district }: { item: PlaceSearchItem; index?: number; annotation?: string }) { return ( <> {index !== undefined ? {String(index + 1).padStart(2, "0")} : null} {item.name} {annotation} {item.address} > ); } export function GeocodingPanel({ geocoding }: { geocoding: WorkbenchGeocoding }) { const inputId = useId(); const statusId = useId(); const candidateListRef = useRef(null); const changeSelectionRef = useRef(null); const { address, result, results, pending, error, enabled } = geocoding; const mapResult = geocoding.origin === "map"; const selectionCollapsed = Boolean(result && !geocoding.choosing); const hideSelectionHint = Boolean(results?.items.length && enabled && !error && !geocoding.picking); useLayoutEffect(() => { if (selectionCollapsed || !result) return; const list = candidateListRef.current; const selected = list?.querySelector('button[aria-pressed="true"]'); if (!list || !selected) return; const viewport = list.getBoundingClientRect(); const row = selected.getBoundingClientRect(); list.scrollTop += row.top - viewport.top - (list.clientHeight - row.height) / 2; }, [selectionCollapsed, result, results]); const status = !geocoding.configured ? "当前项目暂未配置地名搜索" : error ?? (pending ? (mapResult ? "正在查询取点位置…" : "正在搜索地名…") : !enabled ? "等待地图加载后即可定位" : geocoding.picking ? "点击地图查询地名,按 Esc 结束取点" : result ? "已在地图标出所选位置" : results ? results.items.length ? "选择一个候选位置进行定位" : `${geocoding.region}内未找到匹配位置,请细化关键字` : `搜索${geocoding.region}内地址或地名,支持回车查询`); return ( { event.preventDefault(); void geocoding.search(); }} > 查询地址 geocoding.changeAddress(event.target.value)} placeholder="道路、门牌号或地名" className="h-10 min-w-0 flex-1 rounded-xl border-[var(--surface-border)] bg-[var(--surface-reading)] text-xs shadow-none md:text-xs" autoComplete="street-address" maxLength={120} aria-invalid={Boolean(error)} aria-describedby={statusId} /> 查询 {geocoding.picking ? "结束取点" : "地图取点"} {result && !error ? : null} {status} {results && results.items.length > 0 ? ( {selectionCollapsed ? mapResult ? "取点结果" : "已选位置" : "选择位置"} {selectionCollapsed && !mapResult ? ( { geocoding.changeSelection(); if (event.detail === 0) window.requestAnimationFrame(() => { const list = candidateListRef.current; const selected = list?.querySelector('button[aria-pressed="true"]'); (selected ?? list?.querySelector("button"))?.focus({ preventScroll: true }); }); }}> 更换位置 ) : mapResult ? 地图位置 : {results.truncated ? `前 ${results.total} 个结果` : `共 ${results.total} 个结果`}} {!selectionCollapsed ? {results.items.map((item, index) => ( { geocoding.select(item.id); if (event.detail === 0) window.requestAnimationFrame(() => changeSelectionRef.current?.focus()); }}> ))} : null} {result && selectionCollapsed ? ( 经度 / 纬度 {`${result.longitude.toFixed(6)}, ${result.latitude.toFixed(6)}`} 重新定位 清除结果 ) : null} {selectionCollapsed ? <>已在地图标出所选位置> : results.truncated ? `最多显示 ${MAX_PLACE_SEARCH_RESULTS} 个位置,请细化关键词` : "选择一个候选位置进行定位"} ) : null} ); }
{selectionCollapsed ? <>已在地图标出所选位置> : results.truncated ? `最多显示 ${MAX_PLACE_SEARCH_RESULTS} 个位置,请细化关键词` : "选择一个候选位置进行定位"}