mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
fix(planner): trip places in every manual location field, never at 0,0
The trip's places went into the field's search results as they were. A place without a pin was picked as 0,0, because Number(null) is a finite 0, and a highlight left over from a longer search could point past the shorter list and throw on Enter. The phone's form, the train stations and the car stops were not offered the places at all. The places are now prepared once per form (named, with a real coordinate, each once) and handed to every location field of the manual tab, on the desktop and on the phone. The field shows them while it is empty or holds fewer than three characters, narrowed by what was typed, and derives that list rather than writing it into the search results, so a late search answer cannot replace it and a place already picked does not reopen it.
This commit is contained in:
@@ -16,7 +16,12 @@ interface Props {
|
||||
onChange: (loc: LocationPoint | null) => void
|
||||
placeholder?: string
|
||||
style?: React.CSSProperties
|
||||
places?: { name: string; lat: number; lng: number }[]
|
||||
/**
|
||||
* The trip's own places, offered while the field is empty or holds fewer than three
|
||||
* characters, the way the transit search offers them (#2468). Typing narrows them by
|
||||
* name; from the third character on the map search answers instead.
|
||||
*/
|
||||
places?: LocationPoint[]
|
||||
}
|
||||
|
||||
export default function LocationSelect({ value, onChange, placeholder, style, places }: Props) {
|
||||
@@ -47,7 +52,7 @@ export default function LocationSelect({ value, onChange, placeholder, style, pl
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current)
|
||||
const trimmed = query.trim()
|
||||
if (trimmed.length < 3 || (value && trimmed === value.name)) {
|
||||
setResults(trimmed.length < 3 && places ? places : [])
|
||||
setResults([])
|
||||
return
|
||||
}
|
||||
debounceRef.current = setTimeout(async () => {
|
||||
@@ -82,11 +87,20 @@ export default function LocationSelect({ value, onChange, placeholder, style, pl
|
||||
setResults([])
|
||||
}
|
||||
|
||||
// The trip's places stand in for the search below three characters. Derived rather
|
||||
// than written into `results`, so a search answer that lands after the field was
|
||||
// shortened cannot replace them, and a place already picked does not reopen them.
|
||||
const typed = query.trim()
|
||||
const showPicks = !value && typed.length < 3 && !!places?.length
|
||||
const rows = showPicks
|
||||
? (places ?? []).filter(p => !typed || p.name.toLowerCase().includes(typed.toLowerCase()))
|
||||
: results
|
||||
|
||||
const onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!open || results.length === 0) return
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); setHighlight(h => Math.min(h + 1, results.length - 1)) }
|
||||
if (!open || rows.length === 0) return
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); setHighlight(h => Math.min(h + 1, rows.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 === 'Enter' && rows[highlight]) { e.preventDefault(); pick(rows[highlight]) }
|
||||
else if (e.key === 'Escape') setOpen(false)
|
||||
}
|
||||
|
||||
@@ -98,7 +112,7 @@ export default function LocationSelect({ value, onChange, placeholder, style, pl
|
||||
type="text"
|
||||
value={query}
|
||||
placeholder={placeholder ?? t('reservations.searchLocation')}
|
||||
onChange={(e) => { setQuery(e.target.value); setOpen(true); if (value) onChange(null) }}
|
||||
onChange={(e) => { setQuery(e.target.value); setOpen(true); setHighlight(-1); if (value) onChange(null) }}
|
||||
onFocus={() => setOpen(true)}
|
||||
onKeyDown={onKey}
|
||||
className="bg-transparent text-content"
|
||||
@@ -111,14 +125,14 @@ export default function LocationSelect({ value, onChange, placeholder, style, pl
|
||||
)}
|
||||
</div>
|
||||
|
||||
{open && (loading || results.length > 0) && (
|
||||
{open && ((loading && !showPicks) || rows.length > 0) && (
|
||||
<div className="bg-surface-card" style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, border: '1px solid var(--border-primary)', borderRadius: 10, boxShadow: '0 8px 24px rgba(0,0,0,0.18)', maxHeight: 260, overflowY: 'auto', zIndex: 1000 }}>
|
||||
{loading && results.length === 0 && (
|
||||
{loading && !showPicks && rows.length === 0 && (
|
||||
<div className="text-content-faint" style={{ padding: 10, fontSize: 'calc(12px * var(--fs-scale-body, 1))' }}>{t('common.loading')}</div>
|
||||
)}
|
||||
{results.map((r, i) => (
|
||||
{rows.map((r, i) => (
|
||||
<button
|
||||
key={`${r.osm_id || r.google_place_id || i}`}
|
||||
key={showPicks ? `pick:${r.name}:${r.lat}:${r.lng}` : `${r.osm_id || r.google_place_id || i}`}
|
||||
type="button"
|
||||
onClick={() => pick(r)}
|
||||
onMouseEnter={() => setHighlight(i)}
|
||||
|
||||
Reference in New Issue
Block a user