import { useEffect, useRef, useState } from 'react' import { MapPin } from 'lucide-react' import { mapsApi } from '../../api/client' import { useTranslation } from '../../i18n' interface Props { value: string onChange: (value: string) => void placeholder?: string className?: string } // Free-text address input with location autocomplete, backed by the same maps // search as LocationSelect. Unlike LocationSelect the typed text is // authoritative: every keystroke reaches the parent so a hand-written address // is never lost, and picking a suggestion just replaces the text (#1496). export default function AddressInput({ value, onChange, placeholder, className }: Props) { const { t, locale } = useTranslation() const [open, setOpen] = useState(false) const [results, setResults] = useState([]) const [highlight, setHighlight] = useState(-1) const [loading, setLoading] = useState(false) const wrapRef = useRef(null) const debounceRef = useRef | null>(null) useEffect(() => { const handler = (e: MouseEvent) => { if (!wrapRef.current?.contains(e.target as Node)) setOpen(false) } if (open) document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [open]) useEffect(() => () => { if (debounceRef.current) clearTimeout(debounceRef.current) }, []) // Search on typing only (not on focus or external value changes), so opening // a modal with a saved address doesn't fire a request. const search = (text: string) => { if (debounceRef.current) clearTimeout(debounceRef.current) const trimmed = text.trim() if (trimmed.length < 3) { setResults([]); setLoading(false); return } debounceRef.current = setTimeout(async () => { setLoading(true) try { const data = await mapsApi.search(trimmed, locale) setResults(data.places || []) setHighlight(-1) } catch { setResults([]) } finally { setLoading(false) } }, 320) } const pick = (r: any) => { if (debounceRef.current) clearTimeout(debounceRef.current) onChange(r.address || r.name || '') setOpen(false) setResults([]) setLoading(false) } const onKey = (e: React.KeyboardEvent) => { if (!open || results.length === 0) return if (e.key === 'ArrowDown') { e.preventDefault(); setHighlight(h => Math.min(h + 1, results.length - 1)) } else if (e.key === 'ArrowUp') { e.preventDefault(); setHighlight(h => Math.max(h - 1, 0)) } else if (e.key === 'Enter' && highlight >= 0) { e.preventDefault(); pick(results[highlight]) } else if (e.key === 'Escape') setOpen(false) } return (
{ onChange(e.target.value); setOpen(true); search(e.target.value) }} onFocus={() => setOpen(true)} onKeyDown={onKey} className={className} /> {open && (loading || results.length > 0) && (
{loading && results.length === 0 && (
{t('common.loading')}
)} {results.map((r, i) => ( ))}
)}
) }